<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title><![CDATA[ Liecorp: Recent Blog ]]></title>
        <link><![CDATA[ https://liecorp.id/feed/blog ]]></link>
        <description><![CDATA[ Liecorp.id is a personal website built and run by Hendrik Lie to showcase his projects and achievements. ]]></description>
        <language>en</language>
        <pubDate>2026-04-28 19:54:08</pubDate>

                    <item>
                <title><![CDATA[Autolaunch tmux with custom layout under vscode]]></title>
                <link>https://liecorp.id/blog/autolaunch-tmux-with-custom-layout-under-vscode-ee30ad23</link>
                <description><![CDATA[<blockquote>
<p>This post is originally published as a <a
href="https://gist.github.com/liehendr/fd40bd338209d5483720faaf3244c8db">Github
Gist</a> last year at 2023-07-22. I re-posted it with modifications here
simply because I feel like documenting it here.</p>
</blockquote>
<p>As a part of our company workflow, we requires a tmux setup that
would automatically open specific file locations with a certain layout
when vscode terminal is open.</p>
<p>Let’s implement them.</p>
<h2 id="issues">Issues</h2>
<p>Our company requires us work with several project directories. For
simplification, we assume that each project directories have unique
directory names. Below are example arbitrary directory path under which
we will have to work on:</p>
<ul>
<li><code>~/App/core/</code> for backend repository</li>
<li><code>~/App/fe/</code> for frontend repository</li>
<li><code>~/App/deployment/</code> for deployment repository</li>
</ul>
<p>We are working with our codes using <code>vscode</code>, and normally
we access its built-in terminal to run various tools such as
<code>artisan</code>, <code>composer</code>, <code>npm</code>, or even
<code>yarn</code> under our project directories. Sometimes we want the
session to persist, even when vscode fails. Even better, we should also
be able to connect to the vscode terminal session from an external
terminal emulator. This is usually done because we can get wider and
cleaner view using a dedicated terminal emulator, rather than using
built-in vscode terminal.</p>
<p>How do we satisfy those specific use cases?</p>
<h2 id="design-overview">Design Overview</h2>
<p>Since we are working with multiple directories, and we want to have
named sessions we can attach to from either vscode built-in terminal or
a proper terminal emulator, we have to use some form of session
persistence. There are plenty of tools that allow persistent terminal
sessions, such as screen and tmux. In this article, we are going to use
tmux, a modern terminal multiplexer that allows persistence of terminal
sessions.</p>
<p>Since we also have multiple directories, ideally each of those
directories have their own dedicated named sessions. For simplicity, we
will just have to use the directory’s basename for their session names.
Therefore we will have to have the following sessions:
<code>core</code>, <code>fe</code>, and <code>deployment</code>.</p>
<p>We will have one default tmux configuration file that would actually
show us useful information. Then for each of those sessions, we would
have to craft their own config files depending on their use cases and
specific requirements. Since the configuration has to work wherever we
decided to attach the sessions from, the sessions would have to be made
whenever we run any terminal from the first time, in a detached mode.
The config file would also take care of the working directory.</p>
<p>At last, we want to have the persistent session as default behavior
in our <code>vscode</code> terminal. We don’t want to just fired up
<code>vscode</code>, open its terminal, and forgetting to set up tmux,
and then run an important program that requires persistence. Therefore,
if we fire up the <code>vscode</code> terminal, we must also
automatically be attached to a tmux session.</p>
<h2 id="pseudocode">Pseudocode</h2>
<p>To satisfy the requirements in both of our use case and design
overview, we can actually simplify our tasks in just six steps.</p>
<ol type="1">
<li>On startup, check if we’re already under tmux.</li>
<li>If we are not under tmux, check if there are existing session with a
specific name.</li>
<li>If the specified named tmux session is not present, then we make a
new detached named session.</li>
<li>From within the created detached name session, we send keys to it to
load its custom configuration files. The config files would also take
care of their working directory.</li>
<li>Finally, we check if we are running under vscode.</li>
<li>If we are, then we will connect to a tmux session with the same name
as our working vscode directory.</li>
</ol>
<h2 id="a-sane-default-tmux-configuration">A sane default tmux
configuration</h2>
<p><code>tmux</code> actually works out of the box, and can be used
right away. However, the sane configuration can be very esoteric for the
uninitiated. I have to shift through a lot of documentations, just to
get a sane default configuration. I started from <a
href="https://wiki.archlinux.org/title/tmux">Archlinux Wiki</a>, and
then from <a href="https://mutelight.org/practical-tmux">Practical
Tmux</a>, and I managed to compile my current <a
href="https://gitlab.com/heno72/xadf/-/blob/trunk/.config/tmux/tmux.conf">default
configuration</a>.</p>
<p>The gist of my current configuration is as follows:</p>
<div class="content-table">
<table>
<thead>
<tr class="header">
<th style="text-align: left;">Key</th>
<th style="text-align: left;">Action</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: left;"><code>Ctrl+a</code></td>
<td style="text-align: left;">tmux prefix</td>
</tr>
<tr class="even">
<td style="text-align: left;"><code>Ctrl+a ,</code></td>
<td style="text-align: left;">rename current window</td>
</tr>
<tr class="odd">
<td style="text-align: left;"><code>Ctrl+j</code></td>
<td style="text-align: left;">detach from current tmux session</td>
</tr>
<tr class="even">
<td style="text-align: left;"><code>Shift+left</code></td>
<td style="text-align: left;">move to the left tmux window</td>
</tr>
<tr class="odd">
<td style="text-align: left;"><code>Shift+right</code></td>
<td style="text-align: left;">move to the right tmux window</td>
</tr>
<tr class="even">
<td style="text-align: left;"><code>Shift+down</code></td>
<td style="text-align: left;">create a new tmux window</td>
</tr>
<tr class="odd">
<td style="text-align: left;"><code>Alt+left</code></td>
<td style="text-align: left;">swap current window to the left</td>
</tr>
<tr class="even">
<td style="text-align: left;"><code>Alt+right</code></td>
<td style="text-align: left;">swap current window to the right</td>
</tr>
</tbody>
</table>
</div>
<p>Keys separated by a plus sign (‘<code>+</code>’) must be pressed
together, while the one separated with a space means perform the key on
the left first, then release and press the next key in the sequence.</p>
<p>I also set a minimalistic but useful tmux status bar, that will show
you the current session name, current session group, current tmux
windows (that changes color depending on whether they are the active
window or not), your username and machine name, and current machine
time.</p>
<h2 id="allowing-multiple-clients-sharing-the-same-session">Allowing
multiple clients sharing the same session</h2>
<p>Per our requirements, we want the sessions to be accessible from both
the vscode terminal AND any external terminal emulator. Even better,
pair programming is actually possible by having a remote user accessing
the local machine via ssh and access the same terminal session.</p>
<p>To better achieve that, we can spawn two sessions, with the second
session is attached and synchronized to the first main session.
Therefore, any new clients can attach to the first, be it a
<code>vscode</code> terminal, a terminal emulator, or even a remote user
via ssh. To prevent cluttering of ‘zombie’ sessions, that is client
sessions that are no longer attached, we have to do clean up on the
sessions that are connected to the main session.</p>
<p>There are many solutions to achieve the aforementioned
specifications. First there is one from <a
href="https://mutelight.org/practical-tmux#section-6">Practical Tmux</a>
article, and there are two alternative versions in <a
href="https://wiki.archlinux.org/title/tmux#Clients_simultaneously_interacting_with_various_windows_of_a_session">Archlinux
Wiki</a>. The one in Archlinux Wiki is cleaner, and actually handles
cleaning up of zombie sessions better than the one in Practical Tmux
article. Both uses current datetime stamp for children sessions, and the
alternative implementation from Archlinux Wiki uses a bash function
stored in our <code>~/.bashrc</code> file uses unix timestamp to
differentiate parent session and child session.</p>
<p>However I am not satisfied with them for cosmetic reasons. Therefore
I modified the <code>tmx</code> script implementation from both
Practical Tmux article and Archlinux Wiki one, with one major
difference: I use the substring of md5 hash of child session creation
unix timestamp as the child session name. I do that because my tmux
configuration shows both the session name and the session group name for
brevity.</p>
<p>You can see my <a
href="https://gitlab.com/heno72/xadf/-/blob/trunk/.local/bin/tmx">tmx
implementation here</a>.</p>
<p>To use the script, you have to add that script in your path and make
it executable. It can easily be done by exporting your script directory
to your path. For example in my case, to add <code>~/.local/bin</code>
in our <code>$PATH</code> only if it is not yet appended:</p>
<pre><code>test -z &quot;$(echo $PATH|grep &quot;$HOME/\.local/bin&quot;)&quot; \
    &amp;&amp; PATH=&quot;$HOME/.local/bin:$PATH&quot;</code></pre>
<p>Then to make the script executable, you can do:</p>
<pre><code>chmod +x ~/.local/bin/tmx</code></pre>
<p>To make the changes persistent, add them to your <code>.bashrc</code>
or <code>.zshrc</code>.</p>
<p>Therefore, with <code>tmx</code> script, we can create a new named
session on demand, or connect to an existing named session if it is
already present. It is useful since in the next part we will discuss
about the creation of multiple detached named sessions, each with custom
configuration files of the same name located under
<code>~/.tmux.conf.d/sessions/</code>. After they are automatically
generated, we can just connect them from any terminal in our machine
just by running <code>tmx &lt;session_name&gt;</code>. That including
automated deployment of a tmux session from vscode at later stages of
this article.</p>
<h2 id="a-convenience-script-to-generate-named-sessions">A convenience
script to generate named sessions</h2>
<p>Basically, step 1-4 of our pseudocode can be done in just a single
convenience script. Here we will make a script called set_tmux and put
it somewhere in our path. In my case, I put it under
<code>~/.local/bin/</code></p>
<p>The content of the script is as follows (read the comments to see the
workflow):</p>
<pre><code>#!/bin/sh
# Sets named tmux sessions
# usage: set_tmux &lt;session&gt;
# Assumes ~/.tmux.conf.d/sessions/&lt;session&gt;.conf
# file exist

# Loads session $session, if $session is empty,
# then load a session with the same name as the
# first argument. If the first argument is empty,
# then $session name is &#39;main&#39;
tmux_load_config(){
test -z &quot;${session}&quot; &amp;&amp; session=&quot;$1&quot;
test -z &quot;${session}&quot; &amp;&amp; session=&quot;main&quot;
local config_file=&quot;${HOME}/.tmux.conf.d/sessions/${session}.conf&quot;
test -f &quot;$config_file&quot; &amp;&amp; \
    tmux send-keys -t &quot;${session}&quot; \
        &quot;tmux source-file ${config_file}&quot; C-m
}

# If session $1 is not present, make one
configure_tmux(){
local session=&quot;$1&quot;
if test -z &quot;$TMUX&quot; ; then
  tmux ls 2&gt;/dev/null | grep -q &quot;^$session&quot;:
  if test $? -gt 0 ; then
    tmux new-session -d -s &quot;$session&quot; &amp;&amp; \
    tmux_load_config
  fi
fi
}
# Loop through session names and generates named
# sessions
for x in &quot;$@&quot; ;
do
  configure_tmux &quot;$x&quot;
done
</code></pre>
<p>The script is made with the assumption that we have more than one
working directories. For example, if we need one for each of
<code>~/App/core</code>, <code>~/App/fe</code> (front-end directory),
and <code>~/App/deployment</code>, then we can just have to create three
configuration files under <code>~/.tmux.conf.d/sessions/</code>:</p>
<ul>
<li>core.conf</li>
<li>fe.conf</li>
<li>deployment.conf</li>
</ul>
<p>We will discuss the tmux config files later in this article.</p>
<p>The for-loop section basically passess all arguments as session
names. Then for each of them, it would run <code>configure_tmux()</code>
by taking the session name as the argument. The
<code>configure_tmux()</code> function would check with
<code>tmux ls</code> command and piping the output to <code>grep</code>
to determine whether the session of that name is already run or not. If
it is not run already, only then would the function launches a new
session of that name. It would then run <code>tmux_load_config</code>
that basically checks if the config file for that particular session
name is present or not. If it is present, then the config file would be
loaded, otherwise it would be ignored and only default tmux
configuration is loaded.</p>
<p>Then in our shell configurations (either ~/.bashrc, ~/.zshrc, or
both), we just have to set the following lines:</p>
<pre><code>set_tmux core fe deployment</code></pre>
<p>Note that you must ensure that <code>set_tmux</code> is within your
path. See the previous section that discusses <code>tmx</code> script on
how to add this file to your path and make it executable.</p>
<h2 id="custom-tmux-configuration-files">Custom tmux configuration
files</h2>
<p>In this article we assume that you put your session configuration
files under <code>~/.tmux.conf.d/sessions/</code>. For the purpose of
this article, we assume that the session name is the same as the
configuration file name. For example, tmux session <code>core</code>
would have a configuration file under
<code>~/.tmux.conf.d/sessions/core.conf</code>.</p>
<p>This article also assumes that the named tmux sessions are created
with <code>set_tmux</code> script called from our <code>~/.bashrc</code>
or <code>~/.zshrc</code> as described in the previous section. This is
important, because <code>set_tmux</code> actually checks whether
configuration file for <session> that it creates is present in
<code>~/.tmux.conf.d/sessions/&lt;session&gt;.conf</code>. If it is
present, then the script will load the configuration file. Therefore the
config file will only be run once during the creation of those detached
sessions.</p>
<p>Since most of our desired configurations are already set in our
default tmux configuration (mine is located at <a
href="https://gitlab.com/heno72/xadf/-/blob/trunk/.config/tmux/tmux.conf"><code>~/.config/tmux/tmux.conf</code></a>),
we would only need custom configurations for our own projects. In my
case it is usually automated creation of workspace layout. It can be of
windows, or to autorun certain commands during tmux session startup.</p>
<p>The simplest use case might be to automatically open two named
windows in our tmux session: <code>git</code> and <code>npm</code>. To
do that, we basically have to rename the first tmux window to
<code>git</code>, and then we create a new named window
<code>npm</code>. Here’s a simple configuration file that implements
that:</p>
<pre><code># File ~/.tmux.conf.d/sessions/fe.conf
# Rename window :0
rename-window -t :0 &#39;git&#39;

# Make a new named window
new-window -t :1 -n &#39;npm&#39;

# Select first window
selectw -t 0
</code></pre>
<p>However the working directory would still be the working directory of
the spawned terminal. If the first terminal we open is the one in
vscode, it is very likely that its working directory is the project
directory, which is exactly what we want to do. However if you open
another terminal emulator, that might not be the case. Instead it is
likely that the working directory is from your home directory
(<code>$HOME</code> or <code>~/</code>).</p>
<p>We can actually circumvent that by creating new named windows with a
specified working directory with the following command under the config
file:</p>
<pre><code>new-window -t xadf:1 -n &#39;git&#39; -c ~/Documents/xadf</code></pre>
<p>However when we make a new tmux session, there will always be one
default window, usually <code>&lt;session_name&gt;:0</code> (eg.
<code>main:0</code> or simply <code>:0</code>). The command above would
only affect the working directory of new tmux windows. If we want to
change the current working directory of the first window, or any tmux
windows if that matters, is simply by sending key sequences to that
specific tmux window:</p>
<pre><code># Target is a named tmux session &#39;xadf&#39;

# Change directory to ~/Documents/xadf, enter,
# and clear the screen
send-keys -t xadf:0 &#39;cd ~/Documents/xadf&#39; C-m C-l

# Clear the screen, and then display current
# branch&#39;s name and status, then enter
send-keys -t xadf:1 &#39;clear;git status -sb&#39; C-m</code></pre>
<p>In fact we can send any arbitrary commands to a tmux window with that
method. Note that if we don’t include <code>C-m</code>, the command
would just sit in the terminal as if you press those keys on the window.
It is not run yet, as we haven’t send the ENTER key to execute it. The
<code>C-m</code> sequence is equivalent to pressin ENTER. Likewise, the
<code>C-l</code> sequence is the same as pressing <code>Ctrl+l</code> or
running <code>clear</code> command.</p>
<p>Revisiting our <code>fe.conf</code>, we can then have the following
to consistently open a detached named tmux session <code>fe</code>
during a terminal start if it is not present already and have it shows
the correct working directory for both windows:</p>
<pre><code># File ~/.tmux.conf.d/sessions/fe.conf
# Working directory is ~/App/fe

# Rename window :0
rename-window -t :0 &#39;git&#39;

# Send &#39;cd ~/App/fe&#39; to window :0 and clear
send-keys -t :0 &#39;cd ~/App/fe&#39; C-m C-l

# Make a new named window
new-window -t :1 -n &#39;npm&#39; -c ~/App/fe

# Select first window
selectw -t 0
</code></pre>
<p>Then we can create similar configuration files for <code>core</code>
and <code>deployment</code>. We can determine how many tmux windows
would each of them be, and what to name (or not) each of the tmux
windows. We can also send and run arbitrary commands automatically (such
as <code>npm run dev</code>, <code>git pull</code>, or even
<code>git log --oneline --graph</code>) in the desired tmux windows.</p>
<h2 id="automatically-run-tmux-in-vscode-terminal">Automatically run
tmux in vscode terminal</h2>
<p>The last part that we need to implement are step 5-6 of our
pseudocode. We want to test whether we’re running under vscode or not.
Surprisingly, the solution to this is fairly trivial. You can read it
from <a
href="https://stackoverflow.com/a/59231654/12571203">StackOverflow</a>
or <a href="https://askubuntu.com/a/1134191/126560">AskUbuntu</a>.</p>
<p>Basically we want to check whether or not <code>$TERM_PROGRAM</code>
environment variable is set to <code>vscode</code>. If that is true,
then we are in <code>vscode</code>, and we can just connect to a session
with the same name as our working directory using our <code>tmx</code>
script. The implementation of those statements that can be run under
bash and zsh is:</p>
<pre><code>[[ &quot;$TERM_PROGRAM&quot; == &quot;vscode&quot; ]] \
    &amp;&amp; tmx &quot;$(basename &quot;$PWD&quot;)&quot; || true</code></pre>
<p>The last <code>true</code> command is only run if we are not in
vscode. I added it there because in some terminal prompt style, we get a
certain feedback if the last run command gives nonzero exit status. For
example in a default <code>oh-my-zsh</code> installation, the prompt
indicator might turn from green to red the first time I run a terminal
from outside of vscode. That is a minor annoyance for me, especially if
that line is appended at the end of our <code>.bashrc</code> and/or
<code>.zshrc</code>.</p>
<p>The <code>true</code> statement would always return exit status
<code>0</code>, which guarantees a clean exit status at the beginning of
our interactive terminal prompt. That is, assuming that the
<code>tmx</code> script does not terminate with a nonzero exit
status.</p>
<h2 id="conclusion">Conclusion</h2>
<p>To improve our developer workflows, and addresses the issues explored
in this article, we implemented the following measures:</p>
<ol type="1">
<li>We implemented a sane, minimalistic, and usable default tmux
configuration in <code>~/.config/tmux/tmux.conf</code>. It can be
downloaded from <a
href="https://gitlab.com/heno72/xadf/-/raw/trunk/.config/tmux/tmux.conf">gitlab</a>.</li>
<li>To allow a persistant terminal session that can be accessed from any
terminal or ssh connection in our developer’s machine, we implemented a
script to generate a new named tmux session on demand, or connect to an
existing named tmux session if already present. The script can be
obtained from <a
href="https://gitlab.com/heno72/xadf/-/raw/trunk/.local/bin/tmx">gitlab</a>.
To use it, add the file to your path and make it executable.</li>
<li>To automatically launch arbitrary amount of named tmux sessions on
terminal start up if they are not run already, and load the associated
tmux configuration files under <code>~/tmux.conf.d/sessions/</code>, we
implemented a <code>set_tmux</code> script. The script can be obtained
from <a
href="https://gitlab.com/heno72/xadf/-/raw/trunk/.local/bin/set_tmux">gitlab</a>.
To use it, add the file to your path and make it executable.</li>
<li>Developers are encouraged to create their own tmux config files and
place it under <code>~/.tmux.conf.d/sessions/</code> with the same name
as their project directory and file extension <code>.conf</code>, as
every developer have their own specific workflows and preferences.</li>
<li>In <code>~/.bashrc</code> or <code>~/.zshrc</code>, adds the
attached snippets (<a href="#appendix">Appendix</a>) to enable our
setup. Modify as you see fit.</li>
</ol>
<h2 id="appendix">Appendix</h2>
<pre><code># Add ~/.local/bin/ to $PATH if not already
# present. It should contains tmx and set_tmux
# script for our setup
test -z &quot;$(echo $PATH|grep &quot;$HOME/\.local/bin&quot;)&quot; \
    &amp;&amp; PATH=&quot;$HOME/.local/bin:$PATH&quot;

# Change the tmux session names according to your
# needs. Also prepare custom configurations under
# ~/.tmux.conf.d/sessions/ if desired
set_tmux core fe deployment

# Check if we are under vscode. If yes, run tmux
# and/or attach to an existing tmux session
[[ &quot;$TERM_PROGRAM&quot; == &quot;vscode&quot; ]] \
    &amp;&amp; tmx &quot;$(basename &quot;$PWD&quot;)&quot; || true
</code></pre>]]></description>
                <author><![CDATA[Hendrik Lie]]></author>
                <guid>a796531a-aa63-4799-a1c5-b658ca2d14ea</guid>
                <pubDate>Fri, 05 Jul 2024 23:12:46 +0700</pubDate>
            </item>
                    <item>
                <title><![CDATA[Laravel Eloquent Relationships make codes cleaner]]></title>
                <link>https://liecorp.id/blog/laravel-eloquent-relationships-make-codes-cleaner-bc203707</link>
                <description><![CDATA[<p>Just now I dare myself to actually implement <a
href="https://laravel.com/docs/10.x/eloquent-relationships">Laravel’s
eloquent relationships</a> to this site. Previously I was too afraid,
thinking that it would be too complicated. I resorted to <a
href="https://laravel.com/docs/10.x/queries">query builder</a> instead.
It took me some efforts to join from <code>post_types</code> and
<code>users</code> tables to <code>posts</code> table, and I am amazed
at how I decided to take a more complicated route to achieve pretty much
the same results.</p>
<p>Yes, Eloquent relationships are more succinct when we have
implemented it to our models. In the following sections I will try to
demonstrate the difference between the two and how eloquent creates
cleaner code.</p>
<h2 id="before-eloquent---query-builder">Before Eloquent - Query
Builder</h2>
<p>What we did to fetch a post was originally like the following:</p>
<div class="sourceCode" id="cb1"><pre
class="sourceCode php"><code class="sourceCode php"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="kw">use</span> <span class="cn">A</span>pp\<span class="cn">M</span>odels\<span class="cn">P</span>ost<span class="ot">;</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="co">// Fetch published posts of a certain type,</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a><span class="co">// the amount of which is limited</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a><span class="va">$type</span> <span class="op">=</span> <span class="st">&#39;news&#39;</span><span class="ot">;</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a><span class="va">$limit</span> <span class="op">=</span> <span class="dv">15</span><span class="ot">;</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a><span class="cn">P</span>ost::select(<span class="st">&#39;posts.id&#39;</span><span class="ot">,</span></span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a>           <span class="st">&#39;posts.created_at as created&#39;</span><span class="ot">,</span></span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a>           <span class="st">&#39;post_types.code as type&#39;</span><span class="ot">,</span></span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a>           <span class="st">&#39;post_types.name as type_name&#39;</span><span class="ot">,</span></span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a>           <span class="st">&#39;posts.slug&#39;</span><span class="ot">,</span></span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a>           <span class="st">&#39;posts.title&#39;</span><span class="ot">,</span></span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a>           <span class="st">&#39;users.name as author&#39;</span><span class="ot">,</span></span>
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a>           <span class="st">&#39;posts.body&#39;</span>)</span>
<span id="cb1-16"><a href="#cb1-16" aria-hidden="true" tabindex="-1"></a>    -&gt;where(<span class="st">&#39;is_published&#39;</span><span class="ot">,</span><span class="dv">1</span>)</span>
<span id="cb1-17"><a href="#cb1-17" aria-hidden="true" tabindex="-1"></a>    -&gt;where(<span class="st">&#39;post_types.code&#39;</span><span class="ot">,</span><span class="va">$type</span>)</span>
<span id="cb1-18"><a href="#cb1-18" aria-hidden="true" tabindex="-1"></a>    -&gt;orderBy(<span class="st">&#39;created&#39;</span><span class="ot">,</span><span class="st">&#39;desc&#39;</span>)</span>
<span id="cb1-19"><a href="#cb1-19" aria-hidden="true" tabindex="-1"></a>    -&gt;<span class="fu">join</span>(<span class="st">&#39;users&#39;</span><span class="ot">,</span></span>
<span id="cb1-20"><a href="#cb1-20" aria-hidden="true" tabindex="-1"></a>           <span class="st">&#39;users.id&#39;</span><span class="ot">,</span></span>
<span id="cb1-21"><a href="#cb1-21" aria-hidden="true" tabindex="-1"></a>           <span class="st">&#39;=&#39;</span><span class="ot">,</span><span class="st">&#39;posts.users_id&#39;</span>)</span>
<span id="cb1-22"><a href="#cb1-22" aria-hidden="true" tabindex="-1"></a>    -&gt;<span class="fu">join</span>(<span class="st">&#39;post_types&#39;</span><span class="ot">,</span></span>
<span id="cb1-23"><a href="#cb1-23" aria-hidden="true" tabindex="-1"></a>           <span class="st">&#39;post_types.id&#39;</span><span class="ot">,</span></span>
<span id="cb1-24"><a href="#cb1-24" aria-hidden="true" tabindex="-1"></a>           <span class="st">&#39;=&#39;</span><span class="ot">,</span><span class="st">&#39;posts.post_types_id&#39;</span>)</span>
<span id="cb1-25"><a href="#cb1-25" aria-hidden="true" tabindex="-1"></a>    -&gt;limit(<span class="va">$limit</span>)</span>
<span id="cb1-26"><a href="#cb1-26" aria-hidden="true" tabindex="-1"></a>    -&gt;get()<span class="ot">;</span></span>
<span id="cb1-27"><a href="#cb1-27" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-28"><a href="#cb1-28" aria-hidden="true" tabindex="-1"></a><span class="co">// Fetch a particular post by selecting its</span></span>
<span id="cb1-29"><a href="#cb1-29" aria-hidden="true" tabindex="-1"></a><span class="co">// ID</span></span>
<span id="cb1-30"><a href="#cb1-30" aria-hidden="true" tabindex="-1"></a><span class="va">$id</span><span class="op">=</span><span class="st">&#39;put-post-id-here&#39;</span><span class="ot">;</span></span>
<span id="cb1-31"><a href="#cb1-31" aria-hidden="true" tabindex="-1"></a><span class="cn">P</span>ost::select(<span class="st">&#39;posts.id&#39;</span><span class="ot">,</span></span>
<span id="cb1-32"><a href="#cb1-32" aria-hidden="true" tabindex="-1"></a>    <span class="st">&#39;posts.created_at as created&#39;</span><span class="ot">,</span></span>
<span id="cb1-33"><a href="#cb1-33" aria-hidden="true" tabindex="-1"></a>    <span class="st">&#39;post_types.code as type&#39;</span><span class="ot">,</span></span>
<span id="cb1-34"><a href="#cb1-34" aria-hidden="true" tabindex="-1"></a>    <span class="st">&#39;posts.slug&#39;</span><span class="ot">,</span></span>
<span id="cb1-35"><a href="#cb1-35" aria-hidden="true" tabindex="-1"></a>    <span class="st">&#39;posts.title&#39;</span><span class="ot">,</span></span>
<span id="cb1-36"><a href="#cb1-36" aria-hidden="true" tabindex="-1"></a>    <span class="st">&#39;users.name as author&#39;</span><span class="ot">,</span></span>
<span id="cb1-37"><a href="#cb1-37" aria-hidden="true" tabindex="-1"></a>    <span class="st">&#39;users.id as author_id&#39;</span><span class="ot">,</span></span>
<span id="cb1-38"><a href="#cb1-38" aria-hidden="true" tabindex="-1"></a>    <span class="st">&#39;posts.body&#39;</span><span class="ot">,</span></span>
<span id="cb1-39"><a href="#cb1-39" aria-hidden="true" tabindex="-1"></a>    <span class="st">&#39;posts.is_published&#39;</span>)</span>
<span id="cb1-40"><a href="#cb1-40" aria-hidden="true" tabindex="-1"></a>        -&gt;where(<span class="st">&#39;posts.id&#39;</span><span class="ot">,</span><span class="va">$id</span>)</span>
<span id="cb1-41"><a href="#cb1-41" aria-hidden="true" tabindex="-1"></a>        -&gt;<span class="fu">join</span>(<span class="st">&#39;users&#39;</span><span class="ot">,</span></span>
<span id="cb1-42"><a href="#cb1-42" aria-hidden="true" tabindex="-1"></a>               <span class="st">&#39;users.id&#39;</span><span class="ot">,</span></span>
<span id="cb1-43"><a href="#cb1-43" aria-hidden="true" tabindex="-1"></a>               <span class="st">&#39;=&#39;</span><span class="ot">,</span><span class="st">&#39;posts.users_id&#39;</span>)</span>
<span id="cb1-44"><a href="#cb1-44" aria-hidden="true" tabindex="-1"></a>        -&gt;<span class="fu">join</span>(<span class="st">&#39;post_types&#39;</span><span class="ot">,</span></span>
<span id="cb1-45"><a href="#cb1-45" aria-hidden="true" tabindex="-1"></a>               <span class="st">&#39;post_types.id&#39;</span><span class="ot">,</span></span>
<span id="cb1-46"><a href="#cb1-46" aria-hidden="true" tabindex="-1"></a>               <span class="st">&#39;=&#39;</span><span class="ot">,</span><span class="st">&#39;posts.post_types_id&#39;</span>)</span>
<span id="cb1-47"><a href="#cb1-47" aria-hidden="true" tabindex="-1"></a>        -&gt;firstOrFail()<span class="ot">;</span></span></code></pre></div>
<p>The joins are necessary for me to be able to access values of its
post types and author information.</p>
<h2 id="sets-up-eloquent-relationships">Sets up Eloquent
Relationships</h2>
<p>Now let us implement Eloquent, by first adding the required functions
to our models. First we want to access posts from our User model.</p>
<div class="sourceCode" id="cb2"><pre
class="sourceCode php"><code class="sourceCode php"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="kw">namespace</span> <span class="cn">A</span>pp\<span class="cn">M</span>odels<span class="ot">;</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a><span class="kw">use</span> <span class="cn">I</span>lluminate\<span class="cn">D</span>atabase\<span class="cn">E</span>loquent\<span class="cn">M</span>odel<span class="ot">;</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a><span class="kw">use</span> <span class="cn">I</span>lluminate\<span class="cn">D</span>atabase\<span class="cn">E</span>loquent\<span class="cn">R</span>elations\<span class="cn">H</span>asMany<span class="ot">;</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a><span class="kw">class</span> <span class="cn">U</span>ser <span class="kw">extends</span> <span class="cn">A</span>uthenticatable</span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a>{</span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a>    <span class="co">/**</span></span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a><span class="co">     * Get the posts for this user</span></span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a><span class="co">     */</span></span>
<span id="cb2-11"><a href="#cb2-11" aria-hidden="true" tabindex="-1"></a>    <span class="kw">public</span> <span class="kw">function</span> posts()<span class="ot">:</span> <span class="cn">H</span>asMany</span>
<span id="cb2-12"><a href="#cb2-12" aria-hidden="true" tabindex="-1"></a>    {</span>
<span id="cb2-13"><a href="#cb2-13" aria-hidden="true" tabindex="-1"></a>        <span class="cf">return</span> <span class="va">$this</span>-&gt;hasMany(</span>
<span id="cb2-14"><a href="#cb2-14" aria-hidden="true" tabindex="-1"></a>                    <span class="cn">P</span>ost::<span class="kw">class</span><span class="ot">,</span></span>
<span id="cb2-15"><a href="#cb2-15" aria-hidden="true" tabindex="-1"></a>                    <span class="st">&#39;users_id&#39;</span>)<span class="ot">;</span></span>
<span id="cb2-16"><a href="#cb2-16" aria-hidden="true" tabindex="-1"></a>    }</span>
<span id="cb2-17"><a href="#cb2-17" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
<p>Now we also want to do the same from our PostType model.</p>
<div class="sourceCode" id="cb3"><pre
class="sourceCode php"><code class="sourceCode php"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="kw">namespace</span> <span class="cn">A</span>pp\<span class="cn">M</span>odels<span class="ot">;</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a><span class="kw">use</span> <span class="cn">I</span>lluminate\<span class="cn">D</span>atabase\<span class="cn">E</span>loquent\<span class="cn">R</span>elations\<span class="cn">H</span>asMany<span class="ot">;</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a><span class="kw">use</span> <span class="cn">I</span>lluminate\<span class="cn">D</span>atabase\<span class="cn">E</span>loquent\<span class="cn">M</span>odel<span class="ot">;</span></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a><span class="kw">class</span> <span class="cn">P</span>ostType <span class="kw">extends</span> <span class="cn">M</span>odel</span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a>{</span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a>    <span class="co">/**</span></span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a><span class="co">     * Get the posts for this PostType</span></span>
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true" tabindex="-1"></a><span class="co">     */</span></span>
<span id="cb3-11"><a href="#cb3-11" aria-hidden="true" tabindex="-1"></a>    <span class="kw">public</span> <span class="kw">function</span> posts()<span class="ot">:</span> <span class="cn">H</span>asMany</span>
<span id="cb3-12"><a href="#cb3-12" aria-hidden="true" tabindex="-1"></a>    {</span>
<span id="cb3-13"><a href="#cb3-13" aria-hidden="true" tabindex="-1"></a>        <span class="cf">return</span> <span class="va">$this</span>-&gt;hasMany(</span>
<span id="cb3-14"><a href="#cb3-14" aria-hidden="true" tabindex="-1"></a>                    <span class="cn">P</span>ost::<span class="kw">class</span><span class="ot">,</span></span>
<span id="cb3-15"><a href="#cb3-15" aria-hidden="true" tabindex="-1"></a>                    <span class="st">&#39;post_types_id&#39;</span>)<span class="ot">;</span></span>
<span id="cb3-16"><a href="#cb3-16" aria-hidden="true" tabindex="-1"></a>    }</span>
<span id="cb3-17"><a href="#cb3-17" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
<p>Note that we use the <code>hasMany</code> methods to define that each
users and post types can have many posts. We also supplied our foreign
keys <code>users_id</code> and <code>post_types_id</code> because we
used nonstandard column names in our table migrations. Eloquent would
normally use snake case of our model name and append it with
<code>_id</code>. So for example, if in our <code>posts</code> table,
our foreign key for post type is <code>post_type_id</code>, we can just
return <code>$this-&gt;hasMany(Post::class);</code> and the method would
just work.</p>
<p>Now we need to define the inverse of <code>hasMany</code> in our
<code>Post</code> model. To define the inverse of <code>hasMany</code>
we can use <code>belongsTo</code> method:</p>
<div class="sourceCode" id="cb4"><pre
class="sourceCode php"><code class="sourceCode php"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="kw">namespace</span> <span class="cn">A</span>pp\<span class="cn">M</span>odels<span class="ot">;</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a><span class="kw">use</span> <span class="cn">I</span>lluminate\<span class="cn">D</span>atabase\<span class="cn">E</span>loquent\<span class="cn">R</span>elations\<span class="cn">B</span>elongsTo<span class="ot">;</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a><span class="kw">use</span> <span class="cn">I</span>lluminate\<span class="cn">D</span>atabase\<span class="cn">E</span>loquent\<span class="cn">M</span>odel<span class="ot">;</span></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a><span class="kw">class</span> <span class="cn">P</span>ost <span class="kw">extends</span> <span class="cn">M</span>odel</span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a>{</span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a>    <span class="co">/**</span></span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a><span class="co">     * Get the owner of a post</span></span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a><span class="co">     */</span></span>
<span id="cb4-11"><a href="#cb4-11" aria-hidden="true" tabindex="-1"></a>    <span class="kw">public</span> <span class="kw">function</span> user()<span class="ot">:</span> <span class="cn">B</span>elongsTo</span>
<span id="cb4-12"><a href="#cb4-12" aria-hidden="true" tabindex="-1"></a>    {</span>
<span id="cb4-13"><a href="#cb4-13" aria-hidden="true" tabindex="-1"></a>        <span class="cf">return</span> <span class="va">$this</span>-&gt;belongsTo(</span>
<span id="cb4-14"><a href="#cb4-14" aria-hidden="true" tabindex="-1"></a>                    <span class="cn">U</span>ser::<span class="kw">class</span><span class="ot">,</span></span>
<span id="cb4-15"><a href="#cb4-15" aria-hidden="true" tabindex="-1"></a>                    <span class="st">&#39;users_id&#39;</span>)<span class="ot">;</span></span>
<span id="cb4-16"><a href="#cb4-16" aria-hidden="true" tabindex="-1"></a>    }</span>
<span id="cb4-17"><a href="#cb4-17" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-18"><a href="#cb4-18" aria-hidden="true" tabindex="-1"></a>    <span class="co">/**</span></span>
<span id="cb4-19"><a href="#cb4-19" aria-hidden="true" tabindex="-1"></a><span class="co">     * Get the type of a post</span></span>
<span id="cb4-20"><a href="#cb4-20" aria-hidden="true" tabindex="-1"></a><span class="co">     */</span></span>
<span id="cb4-21"><a href="#cb4-21" aria-hidden="true" tabindex="-1"></a>    <span class="kw">public</span> <span class="kw">function</span> type()<span class="ot">:</span> <span class="cn">B</span>elongsTo</span>
<span id="cb4-22"><a href="#cb4-22" aria-hidden="true" tabindex="-1"></a>    {</span>
<span id="cb4-23"><a href="#cb4-23" aria-hidden="true" tabindex="-1"></a>        <span class="cf">return</span> <span class="va">$this</span>-&gt;belongsTo(</span>
<span id="cb4-24"><a href="#cb4-24" aria-hidden="true" tabindex="-1"></a>                    <span class="cn">P</span>ostType::<span class="kw">class</span><span class="ot">,</span></span>
<span id="cb4-25"><a href="#cb4-25" aria-hidden="true" tabindex="-1"></a>                    <span class="st">&#39;post_types_id&#39;</span>)<span class="ot">;</span></span>
<span id="cb4-26"><a href="#cb4-26" aria-hidden="true" tabindex="-1"></a>    }</span>
<span id="cb4-27"><a href="#cb4-27" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
<h2 id="accessing-related-models-with-eloquent">Accessing related models
with Eloquent</h2>
<p>Now we can access our post’s user and type effortlessly:</p>
<div class="sourceCode" id="cb5"><pre
class="sourceCode php"><code class="sourceCode php"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="kw">use</span> <span class="cn">A</span>pp\<span class="cn">M</span>odels\<span class="cn">P</span>ost<span class="ot">;</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="co">// Get the model of the post&#39;s owner</span></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a><span class="cn">P</span>ost::first()-&gt;user<span class="ot">;</span></span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a><span class="co">//Get the model of our post type</span></span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a><span class="cn">P</span>ost::first()-&gt;type</span></code></pre></div>
<p>Then if we want to say, get the name of the post’s author, or if we
want to get the code of our post type, we can do:</p>
<div class="sourceCode" id="cb6"><pre
class="sourceCode php"><code class="sourceCode php"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="kw">use</span> <span class="cn">A</span>pp\<span class="cn">M</span>odels\<span class="cn">P</span>ost<span class="ot">;</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a><span class="co">// Get the name of the post&#39;s author</span></span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a><span class="cn">P</span>ost::first()-&gt;user-&gt;name<span class="ot">;</span></span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true" tabindex="-1"></a><span class="co">// Get the code of the post&#39;s type</span></span>
<span id="cb6-7"><a href="#cb6-7" aria-hidden="true" tabindex="-1"></a><span class="cn">P</span>ost::first()-&gt;type-&gt;code<span class="ot">;</span></span></code></pre></div>
<p>Or even accessing posts created by a particular user:</p>
<div class="sourceCode" id="cb7"><pre
class="sourceCode php"><code class="sourceCode php"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="kw">use</span> <span class="cn">A</span>pp\<span class="cn">M</span>odels\<span class="cn">U</span>ser<span class="ot">;</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a><span class="cn">U</span>ser::first()-&gt;posts<span class="ot">;</span></span></code></pre></div>
<p>Or get posts of a certain post type:</p>
<div class="sourceCode" id="cb8"><pre
class="sourceCode php"><code class="sourceCode php"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="kw">use</span> <span class="cn">A</span>pp\<span class="cn">M</span>odels\<span class="cn">P</span>ostType<span class="ot">;</span></span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a><span class="cn">P</span>ostType::first()-&gt;posts<span class="ot">;</span></span></code></pre></div>
<p>We can even use query builder alongside with Eloquent. The following
achieves similar results as our earlier two query builder examples.</p>
<div class="sourceCode" id="cb9"><pre
class="sourceCode php"><code class="sourceCode php"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="kw">use</span> <span class="cn">A</span>pp\<span class="cn">M</span>odels\<span class="cn">P</span>ostType<span class="ot">;</span></span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a><span class="kw">use</span> <span class="cn">A</span>pp\<span class="cn">M</span>odels\<span class="cn">P</span>ost<span class="ot">;</span></span>
<span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-4"><a href="#cb9-4" aria-hidden="true" tabindex="-1"></a><span class="co">// Fetch published posts of a certain type,</span></span>
<span id="cb9-5"><a href="#cb9-5" aria-hidden="true" tabindex="-1"></a><span class="co">// the amount of which is limited</span></span>
<span id="cb9-6"><a href="#cb9-6" aria-hidden="true" tabindex="-1"></a><span class="va">$type</span> <span class="op">=</span> <span class="st">&#39;news&#39;</span><span class="ot">;</span></span>
<span id="cb9-7"><a href="#cb9-7" aria-hidden="true" tabindex="-1"></a><span class="va">$limit</span> <span class="op">=</span> <span class="dv">15</span><span class="ot">;</span></span>
<span id="cb9-8"><a href="#cb9-8" aria-hidden="true" tabindex="-1"></a><span class="cn">P</span>ostType::where(<span class="st">&#39;code&#39;</span><span class="ot">,</span><span class="va">$type</span>)</span>
<span id="cb9-9"><a href="#cb9-9" aria-hidden="true" tabindex="-1"></a>    -&gt;first()</span>
<span id="cb9-10"><a href="#cb9-10" aria-hidden="true" tabindex="-1"></a>    -&gt;posts()</span>
<span id="cb9-11"><a href="#cb9-11" aria-hidden="true" tabindex="-1"></a>    -&gt;where(<span class="st">&#39;is_published&#39;</span><span class="ot">,</span><span class="kw">true</span>)</span>
<span id="cb9-12"><a href="#cb9-12" aria-hidden="true" tabindex="-1"></a>    -&gt;orderBy(<span class="st">&#39;created_at&#39;</span><span class="ot">,</span><span class="st">&#39;desc&#39;</span>)</span>
<span id="cb9-13"><a href="#cb9-13" aria-hidden="true" tabindex="-1"></a>    -&gt;limit(<span class="va">$limit</span>)</span>
<span id="cb9-14"><a href="#cb9-14" aria-hidden="true" tabindex="-1"></a>    -&gt;get()<span class="ot">;</span></span>
<span id="cb9-15"><a href="#cb9-15" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-16"><a href="#cb9-16" aria-hidden="true" tabindex="-1"></a><span class="co">// Fetch a particular post by selecting its</span></span>
<span id="cb9-17"><a href="#cb9-17" aria-hidden="true" tabindex="-1"></a><span class="co">// ID</span></span>
<span id="cb9-18"><a href="#cb9-18" aria-hidden="true" tabindex="-1"></a><span class="va">$id</span><span class="op">=</span><span class="st">&#39;put-post-id-here&#39;</span><span class="ot">;</span></span>
<span id="cb9-19"><a href="#cb9-19" aria-hidden="true" tabindex="-1"></a><span class="cn">P</span>ost::where(<span class="st">&#39;id&#39;</span><span class="ot">,</span><span class="va">$id</span>)</span>
<span id="cb9-20"><a href="#cb9-20" aria-hidden="true" tabindex="-1"></a>    -&gt;firstOrFail()<span class="ot">;</span></span></code></pre></div>
<p>The primary difference is that now we don’t have to perform joins.
The difference would be on how we access the attributes.</p>
<p>When using query builder, we aliased certain columns for easy
accessing. For example <code>users.name</code> is aliased to
<code>author</code> when <code>users</code> table is joined to the
<code>posts</code> table in our previous query builder example.
Therefore assume that we store a particular <code>Post</code> model
after the join operation into a variable <code>$post</code>, we can
access the author name attribute by <code>$post-&gt;author</code>.</p>
<p>If the same post model is implementing eloquent relationships like we
described above, there is no need to alias <code>users.id</code> to
<code>author</code>, all we have to do is to get the user of a post, and
access the <code>name</code> attribute:
<code>$post-&gt;user-&gt;name</code>. Therefore we also made
modifications to our controller methods to reflect the changes. We are
also adjusting our blade templates, as some still directly access models
for displaying contents.</p>
<h2 id="conclusion">Conclusion</h2>
<p>The change is done with minimal differences from the perspective of
casual website viewers. However behind the scene the code is being
tidied out. Our code can be cleaner as we no longer have to manually
join database tables.</p>
<p>In the future we wish to implement user roles using eloquent
relationship, and therefore allowing us to conveniently differentiate
user’s permissions based on their role that is accessible directly from
their user model.</p>]]></description>
                <author><![CDATA[Hendrik Lie]]></author>
                <guid>dd404e25-be93-4627-b4db-5f0ed62e7d5d</guid>
                <pubDate>Thu, 23 May 2024 15:18:34 +0700</pubDate>
            </item>
                    <item>
                <title><![CDATA[Repairing old FAT flash drives]]></title>
                <link>https://liecorp.id/blog/repairing-old-fat-flash-drives-b0d4e426</link>
                <description><![CDATA[<p>I have done it many times before, and most of the time I am
reinventing the wheel over and over again. Why not just document it here
and now?</p>
<h2 id="problem">Problem</h2>
<p>Due to the need to interact with some electronics such as speakers
and file transfer to printing places, I have to maintain a number of
flash drives formatted as <code>FAT</code> drives. Despite its rather
popular usage with significantly wide support, <code>FAT</code> is not
exactly the most robust filesystem in the wild. Therefore it is not
uncommon to find filesystem errors when we have to use
<code>FAT</code>.</p>
<p>Since I have been using Linux for many years (from at least 2011),
today all of my computers are using Linux only. Therefore I have the
need to be able to diagnose and deal with damaged <code>FAT</code>
drives. Now how do we do that in Linux?</p>
<h2 id="check-fat-filesystem">Check <code>FAT</code> filesystem</h2>
<p>There are several options, but I mainly use <a
href="https://askubuntu.com/a/147231/126560">this askubuntu answer</a>.
Meanwhile another answer suggests <a
href="https://help.ubuntu.com/community/FilesystemTroubleshooting#dosfstools_-_FAT12.2C_FAT16_and_FAT32_.28vfat.29_filesystem">this
filesystem troubleshooting article</a>. The tool in question is
available in Arch Linux repository as <a
href="https://archlinux.org/packages/core/x86_64/dosfstools/"><code>dosfstools</code></a>.</p>
<p>To install it from Arch Linux repository, we can run the following
command:</p>
<pre class="shell"><code>sudo pacman -S dosfstools</code></pre>
<p>After installing the package, we will have access to the tool
<code>fsck.vfat</code>. For example if we want to check the filesystem
inside <code>/dev/sdb1</code> (change to the appropriate device name),
we can simply run the following:</p>
<pre class="shell"><code>sudo fsck.vfat -tawl /dev/sdb1</code></pre>
<p>The options I use are:</p>
<ul>
<li><code>-t</code>: mark unreadable clusters as bad</li>
<li><code>-a</code>: automatically fix errors</li>
<li><code>-w</code>: write to disk immediately</li>
<li><code>-l</code>: list processed filenames</li>
</ul>
<h2 id="check-for-fake-flash-drives">Check for fake flash drives</h2>
<p>Sometimes the problem is not because of a broken filesystem, but due
to a fake flash drive. Essentially a fake flash drive claims that they
have more storage space than they actually have. When interacting with
such drive, it would at first appear fine if you just store files
smaller than its actual storage capacity. Once you went beyond the
actual storage capacity (because the device claims it still has more
free spaces), it will start overwriting existing files.</p>
<p>For that we will also need a tool called <a
href="https://github.com/AltraMayor/f3"><code>f3</code> - Fight Flash
Fraud</a>. On Arch Linux, you can obtain the tool from the following AUR
package: <a
href="https://aur.archlinux.org/packages/f3"><code>f3</code></a></p>
<p>Then to check whether the announced disk size is true or not (in this
example we use <code>/dev/sdb</code>), we can use:</p>
<pre class="shell"><code>f3probe -t /dev/sdb</code></pre>
<blockquote>
<p>Note that here we did not specify the partition number (eg.
<code>/dev/sdb1</code> or <code>/dev/sdb2</code>), but rather, the block
device itself (<code>/dev/sdb</code>).</p>
</blockquote>
<h2 id="reformat-the-flash-drive">Reformat the flash drive</h2>
<p>Finally, to reformat the partition with <code>FAT</code>:</p>
<pre class="shell"><code>sudo mkfs.vfat -n MYFLASHDRIVE /dev/sdb1</code></pre>
<p>The <code>-n MYFLASHDRIVE</code> option allows us to label the newly
formatted partition as <code>MYFLASHDRIVE</code>.</p>
<p>If we want to specify the FAT type:</p>
<pre class="shell"><code>sudo mkfs.vfat -F 32 -n MYFLASHDRIVE /dev/sdb1</code></pre>
<h2 id="conclusion">Conclusion</h2>
<p>With this article, now you can:</p>
<ol type="1">
<li>Check and repair damages of your <code>FAT</code> drives using
<code>fsck.vfat</code>.</li>
<li>Check if the device you have is announcing their true storage size
or not using <code>f3probe</code>.</li>
<li>Format the device with <code>FAT</code> filesystem using
<code>mkfs.vfat</code>.</li>
</ol>]]></description>
                <author><![CDATA[Hendrik Lie]]></author>
                <guid>348c57fa-8c88-4c23-8da3-16f2119b2d8a</guid>
                <pubDate>Tue, 14 May 2024 11:59:49 +0700</pubDate>
            </item>
                    <item>
                <title><![CDATA[SSH Tunneling, Reverse Tunneling, and Proxy]]></title>
                <link>https://liecorp.id/blog/ssh-tunneling-reverse-tunneling-and-proxy-b00497ae</link>
                <description><![CDATA[<p>In the previous blogs, we have described the basics of our ssh
tunneling infrastructure. The system now serves as our gateway to access
devices from work, for example, and also provide a convenient web proxy
as a replacement of our VPN. In the following sections, we recapped
through some of our use cases:</p>
<ol type="1">
<li>Use of SSH Proxy as a replacement of VPN</li>
<li>Connect to home computer via ssh</li>
<li>Forward services from home to office</li>
<li>Access resources on local LAN from a shared remote</li>
<li>Check which ports are used for reverse SSH tunnel</li>
<li>My personal setup</li>
</ol>
<h2 id="use-of-ssh-proxy-as-a-replacement-of-vpn">Use of SSH Proxy as a
replacement of VPN</h2>
<p>Our browser is <code>google-chrome</code>, and we want it to connect
to our cloud server via a proxy. Therefore we can configure
<code>google-chrome</code> to connect to the internet via the configured
proxy. The configuration should make <code>google-chrome</code>
dependent on the proxy to be present, otherwise it should not be able to
connect to the internet.</p>
<p>Read more <a
href="/blog/ssh-socks-proxy-as-vpn-replacement-7f75a410">in this
article</a>.</p>
<h2 id="connect-to-home-computer-via-ssh">Connect to home computer via
ssh</h2>
<p>The problem with a normal home network is that it is hidden behind a
NAT. Moreover setting up port forwarding is not exactly trivial for
end-user. Therefore, since we have a readily accessible remote server,
we can leverage it for our purpose.</p>
<p>Read more <a href="/blog/connect-to-home-via-ssh-3c167723">in this
article</a>.</p>
<h2 id="forward-services-from-home-to-office">Forward services from home
to office</h2>
<p>We have some services that we would like to be made available at
office for convenience reason. For security purposes we have to use ssh
tunnels instead of exposing the port from our home network’s port
forwarding. It takes one cloud device we control that we utilized as a
bridge between our home computer and office computer.</p>
<p>Read more <a
href="/blog/forward-services-with-ssh-tunnels-d3b91364">in this
article</a>.</p>
<h2 id="access-resources-on-local-lan-from-a-shared-remote">Access
resources on local LAN from a shared remote</h2>
<p>We have certain resources accessible only from home LAN, and we want
to connect to them from anywhere. Given that we have a high availability
cloud server, we can use it as a reverse proxy to access specific
services we wanted to access. A custom configuration is required, and
will be outlined in the article below.</p>
<p>Read more in <a
href="/blog/access-resources-on-local-lan-from-a-shared-remote-eb112618">this
article</a>.</p>
<h2 id="check-which-ports-are-used-for-reverse-ssh-tunnel">Check which
ports are used for reverse SSH tunnel</h2>
<p>Following <a
href="https://unix.stackexchange.com/a/120789/282465">this answer</a>,
we can check which <code>TCP</code> ports are in state
<code>LISTEN</code> and used by <code>sshd</code> using
<code>lsof</code>:</p>
<pre class="shell"><code>$ sudo lsof -iTCP -sTCP:LISTEN | grep sshd</code></pre>
<p>It should output something like the following:</p>
<pre><code>user@remotehost:~$ sudo lsof -iTCP -sTCP:LISTEN|grep sshd
sshd   753  root   3u  IPv4  19286  0t0  TCP *:ssh (LISTEN)
sshd   753  root   4u  IPv6  19298  0t0  TCP *:ssh (LISTEN)
sshd  2039  user   7u  IPv6  24888  0t0  TCP localhost:2222 (LISTEN)
sshd  2039  user   9u  IPv4  24889  0t0  TCP localhost:2222 (LISTEN)
sshd  2039  user  10u  IPv6  24892  0t0  TCP localhost:8385 (LISTEN)
sshd  2039  user  11u  IPv4  24893  0t0  TCP localhost:8385 (LISTEN)
sshd  8141  user   7u  IPv6  60294  0t0  TCP localhost:2223 (LISTEN)
sshd  8141  user   9u  IPv4  60295  0t0  TCP localhost:2223 (LISTEN)</code></pre>
<p>The first two (of user <code>root</code>) are the SSH Daemon, while
the rest (of normal user <code>user</code>) are TCP tunnels, each listed
twice: one for IPv6 and another for IPv4.</p>
<h2 id="my-personal-setup">My personal setup</h2>
<p>Since the basics are already explained on the previous sections, the
next thing we would like to address is how we actually configure our SSH
tunneling infrastructure.</p>
<h3 id="homecomputer">homecomputer</h3>
<p>First we set up file <code>~/.ssh/config</code> as follows:</p>
<pre class="shell"><code>Host cloud-tun
    User                user
    HostName            remotehost
    # connects to ssh at mobile or work
    LocalForward        2223 localhost:2223
    # ssh server
    RemoteForward       2222 localhost:22
    # home syncthing
    RemoteForward       8385 localhost:8384
    # router
    RemoteForward       8090 192.168.100.1:80
    DynamicForward      8257
    ServerAliveInterval 120</code></pre>
<p>Host <code>cloud-tun</code> is configured with four parameters. First
we configures SSH tunnels at port <code>2223</code> that receives
traffic to remote system port <code>2223</code>. Afterwards we
configured a reverse tunnel that forwards traffic to our port
<code>22</code> from remote system port <code>2222</code>. Followed with
another reverse tunnel that forwards traffic to our port
<code>8384</code> from remote system port <code>8385</code>. Then we
opened an encrypted SOCKS port <code>8257</code> that we can use with
our applications.</p>
<p>Then we set up a <code>systemd</code> user service:</p>
<pre class="shell"><code># file ~/.config/systemd/user/cloud-tun.service
[Unit]
Description=SSH tunnel to remotehost

[Service]
Type=simple
Restart=always
RestartSec=10
ExecStart=/usr/bin/ssh -F %h/.ssh/config -N cloud-tun

[Install]
WantedBy=default.target</code></pre>
<p>We activate them by enabling and starting our <code>systemd</code>
user service:</p>
<pre class="shell"><code>$ systemctl --user enable cloud-tun
$ systemctl --user start cloud-tun</code></pre>
<p>The <code>systemd</code> user service would run the command specified
in <code>ExecStart</code>, and would restart after ten seconds of
disconnection. Therefore ensures our service to be available nearly
continuously as long as we are logged in.</p>
<h3 id="workcomputer">workcomputer</h3>
<p>First we set up file <code>~/.ssh/config</code> as follows:</p>
<div class="sourceCode" id="cb6"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="co"># File ~/.ssh/config</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a><span class="ex">Host</span> cloud-tun</span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a>    <span class="ex">User</span>                user</span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a>    <span class="ex">HostName</span>            remotehost</span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a>    <span class="co"># home syncthing</span></span>
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true" tabindex="-1"></a>    <span class="ex">LocalForward</span>        8485 localhost:8385</span>
<span id="cb6-7"><a href="#cb6-7" aria-hidden="true" tabindex="-1"></a>    <span class="co"># connects to home</span></span>
<span id="cb6-8"><a href="#cb6-8" aria-hidden="true" tabindex="-1"></a>    <span class="ex">LocalForward</span>        8022 localhost:2222</span>
<span id="cb6-9"><a href="#cb6-9" aria-hidden="true" tabindex="-1"></a>    <span class="co"># ssh server</span></span>
<span id="cb6-10"><a href="#cb6-10" aria-hidden="true" tabindex="-1"></a>    <span class="ex">RemoteForward</span>       2223 localhost:22</span>
<span id="cb6-11"><a href="#cb6-11" aria-hidden="true" tabindex="-1"></a>    <span class="co"># router</span></span>
<span id="cb6-12"><a href="#cb6-12" aria-hidden="true" tabindex="-1"></a>    <span class="ex">RemoteForward</span>       8091 192.168.100.1:80</span>
<span id="cb6-13"><a href="#cb6-13" aria-hidden="true" tabindex="-1"></a>    <span class="ex">DynamicForward</span>      8257</span>
<span id="cb6-14"><a href="#cb6-14" aria-hidden="true" tabindex="-1"></a>    <span class="ex">ServerAliveInterval</span> 120</span>
<span id="cb6-15"><a href="#cb6-15" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-16"><a href="#cb6-16" aria-hidden="true" tabindex="-1"></a><span class="ex">Host</span> home</span>
<span id="cb6-17"><a href="#cb6-17" aria-hidden="true" tabindex="-1"></a>    <span class="ex">User</span>                pete</span>
<span id="cb6-18"><a href="#cb6-18" aria-hidden="true" tabindex="-1"></a>    <span class="ex">HostName</span>            localhost</span>
<span id="cb6-19"><a href="#cb6-19" aria-hidden="true" tabindex="-1"></a>    <span class="ex">Port</span>                8022</span>
<span id="cb6-20"><a href="#cb6-20" aria-hidden="true" tabindex="-1"></a>    <span class="ex">ServerAliveInterval</span> 120</span>
<span id="cb6-21"><a href="#cb6-21" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-22"><a href="#cb6-22" aria-hidden="true" tabindex="-1"></a><span class="ex">Host</span> xph</span>
<span id="cb6-23"><a href="#cb6-23" aria-hidden="true" tabindex="-1"></a>    <span class="ex">HostName</span>            xenophone</span>
<span id="cb6-24"><a href="#cb6-24" aria-hidden="true" tabindex="-1"></a>    <span class="ex">Port</span>                8022</span></code></pre></div>
<p>Host <code>cloud-tun</code> is configured with four parameters. First
we configures SSH tunnels at port <code>8485</code> that receives
traffic to remote system port <code>8385</code>. Then another SSH tunnel
at port <code>8022</code> that connects to remote system port
<code>2222</code>. Afterwards we configured a reverse tunnel that
forwards traffic to our port <code>22</code> from remote system port
<code>2223</code>. At last we set up an encrypted SOCKS port
<code>8257</code> that we can use with our applications.</p>
<p>Then we configured a <code>systemd</code> user service:</p>
<div class="sourceCode" id="cb7"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="co"># file ~/.config/systemd/user/tunnel.service</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a><span class="ex">[Unit]</span></span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a><span class="va">Description</span><span class="op">=</span>SSH <span class="ex">tunnel</span> to remotehost</span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a><span class="ex">[Service]</span></span>
<span id="cb7-6"><a href="#cb7-6" aria-hidden="true" tabindex="-1"></a><span class="va">Type</span><span class="op">=</span>simple</span>
<span id="cb7-7"><a href="#cb7-7" aria-hidden="true" tabindex="-1"></a><span class="va">Restart</span><span class="op">=</span>always</span>
<span id="cb7-8"><a href="#cb7-8" aria-hidden="true" tabindex="-1"></a><span class="va">RestartSec</span><span class="op">=</span>10</span>
<span id="cb7-9"><a href="#cb7-9" aria-hidden="true" tabindex="-1"></a><span class="va">ExecStart</span><span class="op">=</span>/usr/bin/ssh <span class="ex">-F</span> %h/.ssh/config <span class="at">-N</span> cloud-tun</span>
<span id="cb7-10"><a href="#cb7-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-11"><a href="#cb7-11" aria-hidden="true" tabindex="-1"></a><span class="ex">[Install]</span></span>
<span id="cb7-12"><a href="#cb7-12" aria-hidden="true" tabindex="-1"></a><span class="va">WantedBy</span><span class="op">=</span>default.target</span></code></pre></div>
<p>Afterwards we enable and start the service. We activate them by
enabling and starting our <code>systemd</code> user service:</p>
<pre class="shell"><code>$ systemctl --user enable tunnel
$ systemctl --user start tunnel</code></pre>
<p>The <code>systemd</code> user service would run the command specified
in <code>ExecStart</code>, and would restart after ten seconds of
disconnection. Therefore ensures our service to be available nearly
continuously as long as we are logged in.</p>
<h3 id="remotehost">remotehost</h3>
<p>There is no specific configuration needed at <code>remotehost</code>
but a working <code>ssh</code> server. Ideally it must only use public
key authentication to prevent password brute force hacking.</p>]]></description>
                <author><![CDATA[Hendrik Lie]]></author>
                <guid>4de85037-6848-4f83-81a8-23536f1a8819</guid>
                <pubDate>Mon, 13 May 2024 01:36:12 +0700</pubDate>
            </item>
                    <item>
                <title><![CDATA[Access resources on local LAN from a shared remote]]></title>
                <link>https://liecorp.id/blog/access-resources-on-local-lan-from-a-shared-remote-eb112618</link>
                <description><![CDATA[<p>We have certain resources accessible only from home LAN, and we want
to connect to them from anywhere. Given that we have a high availability
cloud server, we can use it as a reverse proxy to access specific
services we wanted to access. A custom configuration is required, and
will be outlined in the article below.</p>
<h2 id="problem">Problem</h2>
<p>Certain resources, such as a wireless internet router configuration,
is typically only accessible under LAN. Exposing those resources
directly to the internet is impractical because it has no static IP.
Another alternative would be to have an accessible, reachable local
device that can access the LAN resources to act as a proxy to access
them on our behalf.</p>
<h2 id="solution">Solution</h2>
<p>There are two known possible configurations to connect to a resource
available only in a certain LAN from outside.</p>
<ol type="1">
<li>The first one is to let another local device to perform reverse SSH
tunnel that maps a remote port to a certain device in its LAN. Then we
configure a reverse proxy in the remote to make it accessible from the
remote’s webserver.</li>
<li>The second one is just like the first one, except that instead of
configuring a reverse proxy at the remote, we set up a local forwarding
from another machine to the remote machine, mapping the exposed remote
port to another port in the local machine (outside the LAN).</li>
</ol>
<p>We will explore both configurations as they shared the same initial
setup. Therefore this solution section will be divided into three parts:
the first part is where we configure the machine inside our target LAN
that we can control, the second part is where we configure a reverse
proxy in our remote server, and the third part is where we configure
external devices to access the LAN services from outside.</p>
<h3 id="the-device-from-within-target-lan">The Device From Within Target
LAN</h3>
<p>It is actually quite straightforward, as all we need to do is to
prepare a reverse SSH tunnel targeting a locally available service, and
make it available on a port in the remote server. The setup is already
being described in great length on <a
href="/blog/connect-to-home-via-ssh-3c167723">this article</a>.</p>
<p>For example if we want to access our home router, we want to use a
computer inside the same network as our home router, and set up a
reverse SSH tunnel:</p>
<pre class="shell"><code>$ ssh -TNv -R 8090:192.168.100.1:80 user@remotehost</code></pre>
<p>Basically we make <code>192.168.100.1:80</code> in our local network
be accessible from <code>remotehost</code> at an arbitrary port (in this
example, we use port <code>8090</code>). Remember this port as we are
going to simply write it as is in the following sections.</p>
<p>Alternatively, we can just add that to our SSH config file
<code>~/.ssh/config</code>:</p>
<pre><code># file ~/.ssh/config

Host cloud-tun
    User                user
    HostName            remotehost
    RemoteForward       8090 192.168.100.1:80
    ServerAliveInterval 120</code></pre>
<p>Therefore we can just issue:</p>
<pre class="shell"><code>$ ssh -TNv cloud-tun</code></pre>
<h3 id="the-reverse-proxy-on-remote-server">The Reverse Proxy On Remote
Server</h3>
<p>On our server, we can have something like the following
configuration:</p>
<pre class="nginx"><code># Redirect specific sites to use https
# Courtesy of:
# https://serversforhackers.com/c/redirect-http-to-https-nginx
server {
    listen 80;
    listen [::]:80;

    server_name myrouter.domain.tld;

    return 301 https://$server_name$request_uri;
}

# Separate server block for http and https
# The block below only accepts ssl connection
# The block above basically redirects to this block
# Courtesy of:
# https://bobcares.com/blog/nginx-multiple-domains-ssl/
server {
    listen [::]:443 ssl;
    listen 443 ssl;

    server_name myrouter.domain.tld;

    include certs_params_domain.tld_wildcard;

    location / {
        proxy_pass http://127.0.0.1:8090;
        include proxy_params;
    }
}</code></pre>
<p>Then we just have to point our browser to our domain, for example
<code>https://myrouter.domain.tld</code> and our router is accessible
there.</p>
<h3 id="the-device-from-outside-target-lan">The Device From Outside
Target LAN</h3>
<p>It is actually quite straightforward, as all we need to do is to
prepare a SSH tunnel connecting a certain local port to a specified port
at remote. Again, the setup is already being described in great length
on <a href="/blog/connect-to-home-via-ssh-3c167723">this
article</a>.</p>
<p>For example if we want to access our home router and we already
configured the device within the same network as our target service, we
can connect to the service with any device as long as we configured the
following SSH tunnel:</p>
<pre class="shell"><code>$ ssh -TNv -L 8080:localhost:8090 user@remotehost</code></pre>
<blockquote>
<p>Note that the service we want to use in this article is being made
available at port <code>8090</code> of server
<code>remotehost</code>.</p>
</blockquote>
<p>Alternatively, we can just add that to our SSH config file
<code>~/.ssh/config</code>:</p>
<pre><code># file ~/.ssh/config

Host cloud-tun
    User                user
    HostName            remotehost
    LocalForward        8080 localhost:8090
    ServerAliveInterval 120</code></pre>
<p>Therefore we can just issue:</p>
<pre class="shell"><code>$ ssh -TNv cloud-tun</code></pre>
<p>After we set up the tunnel, we can just point our browser to
<code>http://localhost:8080</code>.</p>]]></description>
                <author><![CDATA[Hendrik Lie]]></author>
                <guid>f2d05496-ad46-487f-947e-7035fd814edc</guid>
                <pubDate>Sun, 12 May 2024 04:12:41 +0700</pubDate>
            </item>
                    <item>
                <title><![CDATA[Connect to home via ssh]]></title>
                <link>https://liecorp.id/blog/connect-to-home-via-ssh-3c167723</link>
                <description><![CDATA[<h2 id="problem">Problem</h2>
<p>The problem with a normal home network is that it is hidden behind a
NAT. Moreover setting up port forwarding is not exactly trivial for
end-user. Therefore, since we have a readily accessible remote server,
we can leverage it for our purpose.</p>
<h2 id="solution">Solution</h2>
<p>Basically now there are three devices under our control: -
<code>homecomputer</code> - <code>mymobile</code> -
<code>cloudserver</code></p>
<p>We can then configure <code>cloudserver</code> as a bridge, as we are
in control with it. That way we can, configure a ssh reverse tunnel
<code>homecomputer &gt; cloudserver</code> connection, and also another
ssh reverse tunnel to <code>mymobile &gt; cloudserver</code> . Then we
should also create their corresponding ssh tunnels:
<code>homecomputer &lt; cloudserver</code> and
<code>mymobile &lt; cloudserver</code>.</p>
<h3 id="ssh-tunnel-from-home-and-phone-to-cloud">SSH tunnel from home
and phone to cloud</h3>
<p>From <code>homecomputer</code> we can issue:</p>
<pre class="shell"><code>ssh -R 2222:localhost:22 \
  -L 2223:localhost:2223 \
  user@cloudserver</code></pre>
<p>Here’s how it works:</p>
<ul>
<li>The <code>-R</code> argument tells <code>homecomputer</code> to:
expose port <code>22</code> on <code>localhost</code>, and any
connection from <code>cloudserver</code> port <code>2222</code> should
be accepted by our exposed port.</li>
<li>The <code>-L</code> argument tells <code>homecomputer</code> to:
tunnel any connection made to <code>localhost:2223</code> into port
<code>2223</code> on <code>cloudserver</code>.</li>
</ul>
<p>Then we just have to configure the reverse from
<code>mymobile</code>:</p>
<pre class="shell"><code>ssh -R 2223:localhost:8022 \
  -L 8023:localhost:2222 \
  user@cloudserver</code></pre>
<h3 id="ssh-tunnel-between-home-and-phone-with-cloud-as-a-bridge">SSH
tunnel between home and phone with cloud as a bridge</h3>
<p>This way, from <code>homecomputer</code> to <code>mymobile</code>, I
just have to run:</p>
<pre class="shell"><code>ssh -p 2223 localhost</code></pre>
<p>And from <code>mymobile</code> to <code>homecomputer</code>, I just
have to run:</p>
<pre class="shell"><code>ssh -p 8023 user@localhost</code></pre>
<p>Even better, we should consider also adding <code>-TNv</code>
argument to ssh.</p>
<h3 id="automatic-configuration-on-home">Automatic configuration on
home</h3>
<p>Like the one we write in <a
href="/blog/ssh-socks-proxy-as-vpn-replacement-7f75a410">SSH proxy as
VPN replacement</a>, we can actually configure them as a <a
href="https://wiki.archlinux.org/title/Systemd/User"><code>systemd/user</code>
service</a>:</p>
<div class="sourceCode" id="cb5"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="co"># file ~/.config/systemd/user/remote-tunnel.service</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a><span class="ex">[Unit]</span></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="va">Description</span><span class="op">=</span>SSH <span class="ex">tunnel</span> to domain.tld</span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a><span class="ex">[Service]</span></span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a><span class="va">Type</span><span class="op">=</span>simple</span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a><span class="va">Restart</span><span class="op">=</span>always</span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a><span class="va">RestartSec</span><span class="op">=</span>10</span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a><span class="va">ExecStart</span><span class="op">=</span>/usr/bin/ssh <span class="ex">-F</span> %h/.ssh/config <span class="at">-N</span> remote-tunnel</span>
<span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-11"><a href="#cb5-11" aria-hidden="true" tabindex="-1"></a><span class="ex">[Install]</span></span>
<span id="cb5-12"><a href="#cb5-12" aria-hidden="true" tabindex="-1"></a><span class="va">WantedBy</span><span class="op">=</span>default.target</span></code></pre></div>
<p>This service will be run after user logs in to a session. It will be
killed after the last session of that user is closed. If we want it to
start at boot and would remain be open after the last session of a user
is closed, we can let our service to linger:</p>
<pre class="shell"><code>loginctl enable-linger</code></pre>
<p>Then we write a configuration file as follows:</p>
<div class="sourceCode" id="cb7"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="co"># file ~/.ssh/config</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a><span class="co"># Equivalent to:</span></span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a><span class="co"># ssh \</span></span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a><span class="co">#   -R 2222:localhost:22 \</span></span>
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a><span class="co">#   -L 2223:localhost:2223 \</span></span>
<span id="cb7-6"><a href="#cb7-6" aria-hidden="true" tabindex="-1"></a><span class="co">#   -D 8257 \</span></span>
<span id="cb7-7"><a href="#cb7-7" aria-hidden="true" tabindex="-1"></a><span class="co">#   user@cloudserver</span></span>
<span id="cb7-8"><a href="#cb7-8" aria-hidden="true" tabindex="-1"></a><span class="ex">Host</span> remote-tunnel</span>
<span id="cb7-9"><a href="#cb7-9" aria-hidden="true" tabindex="-1"></a>    <span class="ex">User</span>                user</span>
<span id="cb7-10"><a href="#cb7-10" aria-hidden="true" tabindex="-1"></a>    <span class="ex">HostName</span>            domain.tld</span>
<span id="cb7-11"><a href="#cb7-11" aria-hidden="true" tabindex="-1"></a>    <span class="ex">LocalForward</span>        2223 localhost:2223</span>
<span id="cb7-12"><a href="#cb7-12" aria-hidden="true" tabindex="-1"></a>    <span class="ex">RemoteForward</span>       2222 localhost:22</span>
<span id="cb7-13"><a href="#cb7-13" aria-hidden="true" tabindex="-1"></a>    <span class="ex">DynamicForward</span>      8257</span>
<span id="cb7-14"><a href="#cb7-14" aria-hidden="true" tabindex="-1"></a>    <span class="ex">ServerAliveInterval</span> 120</span></code></pre></div>
<p>It would then enable us to establish the tunnel (bonus the proxy
port) as soon as we login into our account. Then connection to phone is
as simple as:</p>
<div class="sourceCode" id="cb8"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="fu">ssh</span> <span class="at">-p</span> 2223 localhost</span></code></pre></div>
<h3 id="automatic-configuration-on-phone">Automatic configuration on
phone</h3>
<p>I use <a href="https://termux.dev/en/">termux</a> on my phone. From
my phone, we can also set a configuration file:</p>
<div class="sourceCode" id="cb9"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="co"># ssh -R 2223:localhost:8022 \</span></span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a><span class="co">#   -L 8023:localhost:2222 \</span></span>
<span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a><span class="co">#   user@domain.tld</span></span>
<span id="cb9-4"><a href="#cb9-4" aria-hidden="true" tabindex="-1"></a><span class="ex">Host</span> remote-tunnel</span>
<span id="cb9-5"><a href="#cb9-5" aria-hidden="true" tabindex="-1"></a>    <span class="ex">User</span>                user</span>
<span id="cb9-6"><a href="#cb9-6" aria-hidden="true" tabindex="-1"></a>    <span class="ex">HostName</span>            domain.tld</span>
<span id="cb9-7"><a href="#cb9-7" aria-hidden="true" tabindex="-1"></a>    <span class="ex">LocalForward</span>        8023 localhost:2222</span>
<span id="cb9-8"><a href="#cb9-8" aria-hidden="true" tabindex="-1"></a>    <span class="ex">RemoteForward</span>       2223 localhost:8022</span>
<span id="cb9-9"><a href="#cb9-9" aria-hidden="true" tabindex="-1"></a>    <span class="ex">ServerAliveInterval</span> 120</span></code></pre></div>
<p>Therefore we can just do this to initiate connection:</p>
<div class="sourceCode" id="cb10"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="fu">ssh</span> <span class="at">-TNv</span> remote-tunnel</span></code></pre></div>
<p>It can be done with the same <code>tmux</code> setup described in the
<a href="/blog/ssh-socks-proxy-as-vpn-replacement-7f75a410">SSH proxy as
VPN replacement</a> article. Then to connect to our computer, we
invoke:</p>
<div class="sourceCode" id="cb11"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="fu">ssh</span> <span class="at">-p</span> 8023 user@localhost</span></code></pre></div>
<h2 id="conclusion">Conclusion</h2>
<p>The pattern is then:</p>
<pre class="shell"><code>ssh -R port:localhost:hostport \
  -L localport:localhost:remoteport \
  [user@]remotehost</code></pre>
<p>Note that localhost in <code>-L</code> part is actually local to
remotehost, not our localhost. Then for <code>[user@]remotehost</code>
part, it means that the username is optional. If the username is not
given, and only remote host is specified, ssh client will attempt to
connect to the remotehost with the same username as the local computer’s
username.</p>
<p>The map from <code>homecomputer</code> is then:</p>
<pre><code>localhost:22 listens from cloudserver:2222
localhost:2223 forwarded to cloudserver:2223</code></pre>
<p>The map from <code>mymobile</code> is then:</p>
<pre><code>localhost:8022 listens from cloudserver:2223
localhost:8023 forwarded to cloudserver:2222</code></pre>
<p>The map from <code>cloudserver</code> is then:</p>
<pre><code>localhost:2222 to homecomputer:22
localhost:2223 to mymobile:8022
localhost:2223 from homecomputer:2223
localhost:2222 from mymobile:8023</code></pre>
<h2 id="appendix">Appendix</h2>
<p>In an afterthought, we realized that we don’t really need to connect
to and from our phone with this method most of the time. At home, we
would prefer to connect to the phone via our home’s local network,
simply because it’s much faster. Conversely, at work, we would prefer to
connect to the phone via our work’s local network.</p>
<p>We would only need to connect to our home computer from our phone
when we’re outside and our phone is not in the same network as our work
computer. Even then when it does happen, our work computer would not be
normally connected to our home computer.</p>
<p>It means our phone really only need to connect to home computer when
we explicitly told it so. Therefore, our work computer can use a similar
<code>~/.ssh/config</code> as our phone, plus we can set up a
systemd/user service.</p>
<p>At such event where we are outside and our work computer are also on,
we can always connect to our home computer with extra steps from our
phone: go to <code>cloudserver</code>, and then
<code>ssh -p 2222 user@localhost</code> will serve the job well.</p>
<h3 id="ssh-tunnel-between-home-and-office">SSH tunnel between home and
office</h3>
<p>We put the following in our office’s <code>ssh</code>
configuration:</p>
<div class="sourceCode" id="cb16"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb16-1"><a href="#cb16-1" aria-hidden="true" tabindex="-1"></a><span class="co"># file ~/.ssh/config</span></span>
<span id="cb16-2"><a href="#cb16-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb16-3"><a href="#cb16-3" aria-hidden="true" tabindex="-1"></a><span class="ex">Host</span> remote-tun</span>
<span id="cb16-4"><a href="#cb16-4" aria-hidden="true" tabindex="-1"></a>    <span class="ex">User</span>                user</span>
<span id="cb16-5"><a href="#cb16-5" aria-hidden="true" tabindex="-1"></a>    <span class="ex">HostName</span>            domain.tld</span>
<span id="cb16-6"><a href="#cb16-6" aria-hidden="true" tabindex="-1"></a>    <span class="ex">LocalForward</span>        8022 localhost:2222</span>
<span id="cb16-7"><a href="#cb16-7" aria-hidden="true" tabindex="-1"></a>    <span class="ex">RemoteForward</span>       2223 localhost:22</span>
<span id="cb16-8"><a href="#cb16-8" aria-hidden="true" tabindex="-1"></a>    <span class="ex">DynamicForward</span>      8257</span>
<span id="cb16-9"><a href="#cb16-9" aria-hidden="true" tabindex="-1"></a>    <span class="ex">ServerAliveInterval</span> 120</span>
<span id="cb16-10"><a href="#cb16-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb16-11"><a href="#cb16-11" aria-hidden="true" tabindex="-1"></a><span class="ex">Host</span> home</span>
<span id="cb16-12"><a href="#cb16-12" aria-hidden="true" tabindex="-1"></a>    <span class="ex">User</span>                user</span>
<span id="cb16-13"><a href="#cb16-13" aria-hidden="true" tabindex="-1"></a>    <span class="ex">HostName</span>            localhost</span>
<span id="cb16-14"><a href="#cb16-14" aria-hidden="true" tabindex="-1"></a>    <span class="ex">Port</span>                8022</span>
<span id="cb16-15"><a href="#cb16-15" aria-hidden="true" tabindex="-1"></a>    <span class="ex">ServerAliveInterval</span> 120</span></code></pre></div>
<p>Then we set up a <code>systemd</code> user service at work:</p>
<div class="sourceCode" id="cb17"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb17-1"><a href="#cb17-1" aria-hidden="true" tabindex="-1"></a><span class="co"># file ~/.config/systemd/user/tunnel.service</span></span>
<span id="cb17-2"><a href="#cb17-2" aria-hidden="true" tabindex="-1"></a><span class="ex">[Unit]</span></span>
<span id="cb17-3"><a href="#cb17-3" aria-hidden="true" tabindex="-1"></a><span class="va">Description</span><span class="op">=</span>SSH <span class="ex">tunnel</span> to domain.tld</span>
<span id="cb17-4"><a href="#cb17-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb17-5"><a href="#cb17-5" aria-hidden="true" tabindex="-1"></a><span class="ex">[Service]</span></span>
<span id="cb17-6"><a href="#cb17-6" aria-hidden="true" tabindex="-1"></a><span class="va">Type</span><span class="op">=</span>simple</span>
<span id="cb17-7"><a href="#cb17-7" aria-hidden="true" tabindex="-1"></a><span class="va">Restart</span><span class="op">=</span>always</span>
<span id="cb17-8"><a href="#cb17-8" aria-hidden="true" tabindex="-1"></a><span class="va">RestartSec</span><span class="op">=</span>10</span>
<span id="cb17-9"><a href="#cb17-9" aria-hidden="true" tabindex="-1"></a><span class="va">ExecStart</span><span class="op">=</span>/usr/bin/ssh <span class="ex">-F</span> %h/.ssh/config <span class="at">-N</span> remote-tun</span></code></pre></div>
<p>Therefore we can connect to our home computer with:</p>
<pre class="shell"><code>$ ssh home</code></pre>
<h2 id="further-reading">Further reading</h2>
<p>Read more from the <strong>sources below</strong>:</p>
<ul>
<li><a href="https://unix.stackexchange.com/a/46271/282465">ghoti’s
answer on How does reverse SSH tunneling work?</a></li>
<li><a href="https://unix.stackexchange.com/a/118650/282465">erik’s
answer on How does reverse SSH tunneling work?</a></li>
<li><a
href="https://wiki.archlinux.org/title/OpenSSH#Step_1:_start_the_connection">OpenSSH</a></li>
<li><a
href="https://www.baeldung.com/linux/ssh-tunneling-and-proxying">SSH
Tunneling and Proxying</a></li>
<li><a href="/blog/ssh-socks-proxy-as-vpn-replacement-7f75a410">SSH
Socks Proxy as VPN Replacement</a></li>
</ul>]]></description>
                <author><![CDATA[Hendrik Lie]]></author>
                <guid>10309345-9f26-4e4d-bd51-f3a532b6632e</guid>
                <pubDate>Sat, 11 May 2024 09:55:01 +0700</pubDate>
            </item>
                    <item>
                <title><![CDATA[SSH Socks Proxy as VPN Replacement]]></title>
                <link>https://liecorp.id/blog/ssh-socks-proxy-as-vpn-replacement-7f75a410</link>
                <description><![CDATA[<h2 id="problem">Problem</h2>
<p>Our browser is <code>google-chrome</code>, and we want it to connect
to our cloud server via a proxy. Therefore we can configure
<code>google-chrome</code> to connect to the internet via the configured
proxy. The configuration should make <code>google-chrome</code>
dependent on the proxy to be present, otherwise it should not be able to
connect to the internet.</p>
<h2 id="solution">Solution</h2>
<p>There are multitude of layers in our solution. First we need to run a
command to initiate the <code>SOCKS5</code> proxy connection. Then we
need to configure <code>google-chrome</code> to make use of the
<code>SOCKS5</code> proxy port as its web proxy. For convenience
reasons, we have to automate the setup since the first time our shell
starts.</p>
<h3 id="start-the-connection">Start the connection</h3>
<p>It can be done in as simple as:</p>
<pre class="shell"><code>$ ssh -TND &lt;PORT&gt; user@host</code></pre>
<p>The choice of port is arbitrary. I selected one that is unlikely to
be used by any existing services. Argument <code>-T</code> disables
pseudo-tty allocation, while <code>-N</code> disables interactive
prompt. Argument <code>-D PORT</code> is the one that actually do the
creation of an encrypted <code>SOCKS5</code> tunnel. It might also be
useful to have <code>-v</code> passed for verbosity, so we can check
that we are actually connected.</p>
<p>We also want it to be kept alive, and ideally to simplify the
invocation. To keep our connection alive after running it, we can
actually add <code>ServerAliveInterval 120</code> to our
<code>~/.ssh/config</code>. It simply tells our ssh client that in case
it receives no data from the server after a specified interval (here our
interval is 120 seconds), it would send a message to server to get a
response.</p>
<p>The full command we needs to reach our server to initiate our proxy
is then:</p>
<pre class="shell"><code>$ ssh -TNvD &lt;PORT&gt; username@domain.tld</code></pre>
<p>However to simplify that we just need to specify our user name and
host name in our <code>~/.ssh/config</code> file. The equivalent of
<code>-D &lt;PORT&gt;</code> in the config file is the line
<code>DynamicForward &lt;PORT&gt;</code>. Therefore we can compose the
following config file:</p>
<div class="sourceCode" id="cb3"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="co"># file ~/.ssh/config</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="ex">Host</span> proxy</span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a>    <span class="ex">User</span>                username</span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a>    <span class="ex">HostName</span>            domain.tld</span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a>    <span class="ex">ServerAliveInterval</span> 120</span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a>    <span class="ex">DynamicForward</span>      <span class="op">&lt;</span>PORT<span class="op">&gt;</span></span></code></pre></div>
<p>Then all we need to connect to our server is simplified to:</p>
<pre class="shell"><code>$ ssh -TNv proxy</code></pre>
<h3 id="configure-google-chrome-to-use-proxy">Configure Google Chrome to
use proxy</h3>
<p>Apparently, it is rather trivial to force Google Chrome to honor
proxy setting. Arch Linux Wiki suggests to <a
href="https://wiki.archlinux.org/title/OpenSSH#Step_2_(Variant_A):_configure_your_browser_(or_other_programs)">make
the following function</a>:</p>
<div class="sourceCode" id="cb5"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="kw">function</span><span class="fu"> secure_chromium</span> <span class="kw">{</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a>    <span class="va">port</span><span class="op">=&lt;</span>PORT<span class="op">&gt;</span></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a>    <span class="bu">export</span> <span class="va">SOCKS_SERVER</span><span class="op">=</span>localhost:<span class="va">$port</span></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a>    <span class="bu">export</span> <span class="va">SOCKS_VERSION</span><span class="op">=</span>5</span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a>    <span class="ex">chromium</span> <span class="kw">&amp;</span></span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a>    <span class="bu">exit</span></span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a><span class="kw">}</span></span></code></pre></div>
<p>or:</p>
<div class="sourceCode" id="cb6"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="kw">function</span><span class="fu"> secure_chromium</span> <span class="kw">{</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a>    <span class="va">port</span><span class="op">=&lt;</span>PORT<span class="op">&gt;</span></span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a>    <span class="ex">chromium</span> <span class="at">--proxy-server</span><span class="op">=</span><span class="st">&quot;socks://localhost:</span><span class="va">$port</span><span class="st">&quot;</span> <span class="kw">&amp;</span></span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a>    <span class="bu">exit</span></span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a><span class="kw">}</span></span></code></pre></div>
<p>then source them from our <code>~/.bashrc</code>.</p>
<p>In that manner, whenever we want to have a secure connection, we can
just run <code>secure_chromium</code>, and enjoy our secure internet
connection.</p>
<p>However our requirement is that we must make chrome dependent on it,
that it would not be able to connect to the internet without it. The
most obvious solution is to pass
<code>--proxy-server=socks://localhost:&lt;PORT&gt;</code> for each run
of google chrome. But it was too much work, and setting up a custom
script for it is just superfluous.</p>
<p>The solution is actually rather simple, we just have to create the
following line with that argument passed within:</p>
<div class="sourceCode" id="cb7"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="co"># file ~/.config/chrome-flags.conf</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a><span class="ex">--proxy-server=socks://localhost:</span><span class="op">&lt;</span>PORT<span class="op">&gt;</span></span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a><span class="co"># Note that for chromium it is ~/.config/chromium-flags</span></span></code></pre></div>
<p>Therefore our google chrome is constrained to use the proxy
server.</p>
<h3 id="automatic-configuration">Automatic configuration</h3>
<p>Currently we are using our <code>tmux</code> configuration with
convenience scripts provided by <a
href="https://gitlab.com/xadfsh/xadf"><code>xadfsh/xadf</code></a>. We
use <code>set_tmux</code> at the start of our session to launch a single
or a series of named <code>tmux</code> session, and when there’s a
corresponding config file for that named session, it would also be
autoloaded.</p>
<p>Inside a named <code>tmux</code> session’s custom config file
<code>ssh.conf</code>, we included the following lines:</p>
<div class="sourceCode" id="cb8"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="co"># file ~/.tmux.conf.d/sessions/ssh.conf</span></span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a><span class="ex">new-window</span> <span class="at">-t</span> ssh:1 <span class="at">-n</span> <span class="st">&#39;socks&#39;</span></span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a><span class="ex">send-keys</span> <span class="at">-t</span> ssh:1 <span class="st">&#39;clear;ssh -TNv proxy&#39;</span> C-m</span></code></pre></div>
<p>Then we included in our autostart file:</p>
<div class="sourceCode" id="cb9"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="co"># Launch named session ssh among other named sessions</span></span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a><span class="ex">set_tmux</span> ... ssh ...</span></code></pre></div>
<blockquote>
<p><strong>Note:</strong> The script <code>set_tmux</code> is a
convenience script provided <a
href="https://gitlab.com/xadfsh/xadf/-/raw/master/.local/bin/set_tmux?ref_type=heads">here</a>.
To use it, I recommend you to read the script carefully and understand
what it is doing. See the repository <a
href="https://gitlab.com/xadfsh/xadf"><code>xadfsh/xadf</code></a>,
study it, and adapt it to your case if necessary.</p>
</blockquote>
<p>Therefore our connection will automatically be initiated upon
entering our terminal.</p>
<p>However the limitation is that if the connection is severed, we would
be left without a proxy. Therefore one more solution to that is to have
a <code>systemd</code> user service:</p>
<pre class="shell"><code># file ~/.config/systemd/user/proxy.service
[Unit]
Description=SSH tunnel to proxy

[Service]
Type=simple
Restart=always
RestartSec=10
ExecStart=/usr/bin/ssh -F %h/.ssh/config -N proxy</code></pre>
<p>Then we can enable and start it with:</p>
<pre class="shell"><code>systemctl --user enable proxy
systemctl --user start proxy</code></pre>
<p>If this is configured, then we no longer need our <code>tmux</code>
based solution.</p>
<h2 id="sources">Sources</h2>
<ul>
<li><a
href="https://wiki.archlinux.org/title/OpenSSH#Encrypted_SOCKS_tunnel">OpenSSH:
Encrypted SOCKS Tunnel</a></li>
<li><a
href="https://wiki.archlinux.org/title/Chromium#Making_flags_persistent">Chromium:
Making Flags Persistent</a></li>
<li><a
href="https://man.archlinux.org/man/ssh_config.5"><code>man 5 ssh_config</code></a></li>
<li><a
href="https://gitlab.com/xadfsh/xadf"><code>xadfsh/xadf</code></a></li>
</ul>]]></description>
                <author><![CDATA[Hendrik Lie]]></author>
                <guid>e0ca6b20-854b-40c6-8024-d7ccb76de174</guid>
                <pubDate>Fri, 10 May 2024 09:39:10 +0700</pubDate>
            </item>
                    <item>
                <title><![CDATA[Forward Services With SSH Tunnels]]></title>
                <link>https://liecorp.id/blog/forward-services-with-ssh-tunnels-d3b91364</link>
                <description><![CDATA[<h2 id="problem">Problem</h2>
<p>We have some services that we would like to be made available at
office for convenience reason. The service is available from a certain
port in our home computer. For security purposes we have to use ssh
tunnels instead of exposing the port from our home network’s port
forwarding.</p>
<p>At our disposal is a cloud server we control that we utilized as a
bridge between our home computer and office computer. Both our home
computer and work computer are connected to the cloud server. However,
how do we configure the connection between our home computer and office
computer?</p>
<h2 id="case-study">Case Study</h2>
<p>For the purpose of this article, we assumes that we want to expose
our locally available <a href="https://syncthing.net/">Syncthing GUI</a>
configured in <code>homecomputer</code> to listen only to
<code>localhost:8384</code>. The service would not be available from
outside, as our syncthing configuration would reject any connection not
coming from <code>localhost</code>. This is a security feature, that the
service is only available locally, as it allows access to sensitive
files.</p>
<ol type="1">
<li><code>homecomputer</code> is our home computer</li>
<li><code>workcomputer</code> is our work computer</li>
<li><code>cloudserver</code> is our cloud server</li>
</ol>
<h2 id="solution">Solution</h2>
<p>With SSH tunnels, from <code>workcomputer</code> we can access
<code>homecomputer:8384</code> as if we are connecting from
<code>localhost</code>. We can achieve this by setting up a matching
pair of SSH tunnels between <code>homecomputer</code> and
<code>workcomputer</code> through a single common local port on
<code>cloudserver</code>. From <code>homecomputer</code> we configured a
reverse SSH tunnel to <code>cloudserver</code>, and from
<code>workcomputer</code> we configured a SSH tunnel to
<code>cloudserver</code>.</p>
<p>As a bonus, to have a clean URL when accessing from a browser in
<code>workcomputer</code>, we can configure a reverse proxy in a local
<code>nginx</code> at <code>workcomputer</code>. A reverse proxy
configuration would take a certain URL path and would configure a local
port to serve the URL path. By pointing the reverse proxy configuration
to the port in <code>workcomputer</code> we configured as a SSH tunnel
to <code>cloudserver</code> that is forwarded to
<code>homecomputer</code>, we can then access a service available
locally in <code>homecomputer</code> as if we are connecting to it
locally.</p>
<h3 id="ssh-tunnels">SSH Tunnels</h3>
<p>Therefore we just have to configure a SSH reverse tunnel from
<code>homecomputer</code>:</p>
<div class="sourceCode" id="cb1"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="co"># file .ssh/config at homecomputer</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="co"># Equivalent to: ssh -R 8385 localhost:8384 username@cloudserver</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="ex">Host</span> liecorp-tunnel</span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>    <span class="ex">User</span>                username</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a>    <span class="ex">HostName</span>            cloudserver</span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a>    <span class="ex">RemoteForward</span>       8385 localhost:8384</span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a>    <span class="ex">ServerAliveInterval</span> 120</span></code></pre></div>
<p>Then from <code>workcomputer</code> we configured a SSH tunnel:</p>
<pre class="shell"><code># file ~/.ssh/config at workcomputer
# Equivalent to: ssh -L 8485:localhost:8385 username@cloudserver
Host liecorp-tun
    User                username
    HostName            cloudserver
    LocalForward        8485 localhost:8385
    ServerAliveInterval 120</code></pre>
<p>What we did from <code>homecomputer</code> is basically to open a
listening port <code>cloudserver:8385</code> that would correspond to
<code>homecomputer:8384</code>. Then from <code>workcomputer</code> we
forward connection from port <code>workcomputer:8485</code> to
<code>cloudserver:8385</code>, which in turn, is forwarded to
<code>homecomputer:8384</code>. Therefore to reach
<code>homecomputer:8384</code> from <code>workcomputer</code>, we can
point our browser to <code>http://localhost:8485</code> and our service
at <code>homecomputer:8384</code> would be available from
<code>workcomputer</code>.</p>
<h3 id="nginx-reverse-proxy">Nginx reverse proxy</h3>
<p>To make everything tidy, we can even set a reverse proxy
configuration on <code>nginx</code> at <code>workcomputer</code>:</p>
<pre class="nginx"><code># File /etc/nginx/sites-available/server.conf 
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;
    root    /usr/share/nginx/html;
    index   index.html

    include &quot;/etc/nginx/default.d/*.conf&quot;;

    location / {
    try_files $uri $uri.html $uri/ /fallback/index.html;
    }

    error_page 404 /404.html;
    location = /40x.html {
    }

    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
    }

    location /syncwork0/ {
        proxy_pass          http://127.0.0.1:8384/;
        include             proxy_params;
        proxy_read_timeout  600s;
        proxy_send_timeout  600s;
    }
    location /synchome/ {
        proxy_pass          http://127.0.0.1:8485/;
        include             proxy_params;
        proxy_read_timeout  600s;
        proxy_send_timeout  600s;
    }
}</code></pre>
<p>Therefore we can now access <code>homecomputer:8384</code> by
pointing our browser to <code>http://localhost/synchome/</code> instead
of <code>http://localhost:8485/</code>.</p>
<h2 id="conclusion">Conclusion</h2>
<p>To connect to a service available only to <code>localhost</code> at
<code>homecomputer</code> from <code>workcomputer</code>, we:</p>
<ol type="1">
<li>Configure SSH Tunneling from both <code>homecomputer</code> and
<code>workcomputer</code> to <code>cloudserver</code>, where the traffic
would go through <code>cloudserver:8385</code>. The route
<code>cloudserver:8385</code> corresponds to a listening port
<code>homecomputer:8384</code>, and forwarded traffic from
<code>workcomputer:8485</code>. Therefore connecting to
<code>localhost:8485</code> from <code>workcomputer</code> would get us
to <code>localhost:8384</code> at <code>homecomputer</code>. To
<code>syncthing</code>, we are accessing from <code>localhost</code>,
and therefore is allowed to access the service.</li>
<li>Configure Nginx reverse proxy to have a clean URL without having to
type ports. We set up a reverse proxy address for URL
<code>/synchome/</code> that would be served by
<code>http://127.0.0.1:8485/</code>. Therefore we can access the service
from <code>http://localhost/synchome/</code>.</li>
</ol>]]></description>
                <author><![CDATA[Hendrik Lie]]></author>
                <guid>93d91ab2-43dd-4722-b9b3-60abab765b8d</guid>
                <pubDate>Thu, 09 May 2024 20:44:42 +0700</pubDate>
            </item>
                    <item>
                <title><![CDATA[Deterministic vs Non-Deterministic Wallets]]></title>
                <link>https://liecorp.id/blog/deterministic-vs-non-deterministic-wallets</link>
                <description><![CDATA[<div class="disclaimer">
<blockquote>
<p>This page is originally written for and is published on Static Blog
Alpha at 11 August 2022. You can access the original version <a
href="https://arweave.net/f3cjnQSAtpETB06kzzafmLrAk7n2dQrFbO9VMESu244/pages/SBA-007.html">here</a>.</p>
</blockquote>
</div>
<blockquote>
<p>This page is copied from my <a
href="https://gitlab.com/heno72/crypto-research/-/blob/main/docs/deterministic-vs-nondeterministic-wallets.md">gitlab
repository</a>. You may find an updated version of the article
there.</p>
</blockquote>
<p>In this post, we are going to review two classes of crypto wallets.
The first one we will discuss is how addresses used to be managed by a
wallet software, and how such wallets are backed up. The second part
will discuss on how modern wallets handle wallets, and how they are
backed up. Then, the next part will discuss on ways you can secure and
back up your wallets. At last, we will provide a quick review of popular
wallet software available on the market.</p>
<h2 id="early-wallets">Early Wallets</h2>
<p>Prior to 2013,<a href="#fn1" class="footnote-ref" id="fnref1"
role="doc-noteref"><sup>1</sup></a> wallets generate a buffer of fresh
random private keys (typically 100 addresses) for receiving and change
addresses for future use. Such wallets would also offer wallet backups,
to backup all of those addresses. However, once the backup pool
exhausted (the pre-generated 100 addresses are all used up), new
addresses are generated.</p>
<p>As a result, the previous backup is invalidated, and new backup must
be made. This, in effect, requires users to frequently back up their
wallets. The result of losing the wallet without proper backups, or
infrequent ones, results in the loss of funds stored in the non backed
up parts of the wallet.</p>
<p>There are many ways to counter this issue, one of them is the use of
paper wallets. Unlike its name, a paper wallet is not a wallet, but
instead just a single keypair. Because of that, it promotes address
reuse, false sense of security, but also requiring users to manually
handle private keys. Details of its flaws could be found <a
href="https://en.bitcoin.it/wiki/Paper_wallet">here</a>.</p>
<p>Some other means to store your coins are the use of custodial wallets
like Coinbase, web wallets (as a browser extension), cloud storage,
removable media, and physical bitcoins. All of those are explored <a
href="https://en.bitcoin.it/wiki/Storing_bitcoins">here</a> under “Bad
wallet ideas” sections.</p>
<p>The basic idea of backing up a traditional wallet is just by backing
up every and all private keypairs. Personally, it means we are storing a
lot of data that must be reproducible: not a single bit can change, or
you might lose some if not all of your funds. However, the universe is
<a href="https://www.youtube.com/watch?v=AaZ_RSt0KP8">very hostile to
modern computers</a>. With the presence of a <strong>Single Event
Upset</strong> might actually upset your backups or devices storing your
backups, no matter how hard or how well you store them.</p>
<p>Then, there must be a better way to secure and back up your wallets,
right? Right?</p>
<h2 id="the-era-of-seed-phrases">The Era of Seed Phrases</h2>
<p>According to <a
href="https://en.bitcoin.it/wiki/Deterministic_wallet">this article</a>,
by 2019, all good wallets already adopt BIP32, a standard for
hierarchial deterministic wallets. What this kind of wallet does to back
up every and all possible keypairs you will ever use is by deriving all
of your future keypairs from a single starting point known as a seed.
That way, any modern wallet using the same seed and the same derivation
path will always produce the same addresses.</p>
<p>The seed must have a sufficiently high entropy to be secure, and also
convenient to store and back up. However, as discussed in the previous
section, even by storing only the entropy, no computer is safe from a
Single Event Upset. Meanwhile, storing the entropy manually by writing
them down is prone to typo, material damage, hard to read handwritings,
data leakage while printing, and many more attack vectors. Some ideas to
store your coins are also discussed <a
href="https://en.bitcoin.it/wiki/Storing_bitcoins">here</a>, along with
their weaknesses.</p>
<p>Now, how could we store the seed in a manner that is easy to use and
handle, encapsulating a sufficiently high amount of entropy, and has a
very good error correction? Enter the age of <strong>Seed
Phrases</strong><a href="#fn2" class="footnote-ref" id="fnref2"
role="doc-noteref"><sup>2</sup></a>, as a way to losslessly compress our
initial entropy into a list of 12 to 24 words, and can optionally be
further protected with <strong>seed extensions</strong><a href="#fn3"
class="footnote-ref" id="fnref3" role="doc-noteref"><sup>3</sup></a>.
The way they accomplish it is by having a list of words, and have them
represent a number (in <a
href="https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki">BIP39</a>,
<code>0</code> to <code>2047</code>). That way, our 12 to 24 words seed
phrases store a sufficiently large number in an easily human readable
series of natural language words.</p>
<p>The BIP39 wordlist has 2048 words, and for a phrase of 12 random
words, the number of possible combinations would be 2048^12 = 2^132.
Therefore, the phrase would have 132 bits of security. However, the last
word in BIP39 standard is not random, but instead a checksum, hence the
actual 12 words seed phrase has a security of only 128 bits, which is
approximately the same strength of all Bitcoin private keys. It follows
without saying that a 24-words seed phrase has a higher security
compared to a 12-words seed phrase.</p>
<p>That way, for any good wallets that support the generation of and
restoring from seed phrases, you can retrieve your funds back. That
assuming all wallets use the same seed phrase standard and the same
derivation paths, which is not necessarily true. Electrum, for example,
uses <a
href="https://electrum.readthedocs.io/en/latest/seedphrase.html#motivation">a
different seed phrase standard</a> that tries to address the issue
inherent to BIP39 standard.</p>
<p>Furthermore, not all wallets supports <strong>seed
extension</strong>, which makes this matter more complicated. As
mentioned before, a seed extension is a word, or a set of words used to
extend the original entropy, and hence, a more secure way to store your
coins. All and any seed extension combined with all and any seed phrases
will produce valid seeds for any compliant deterministic clients.
Therefore it offers possible deniality in the event where said seed
phrases are compromised. For example, by having funds stored in keypairs
derived from the seed phrases only, or from the seed phrases combined
with fake seed extensions.</p>
<p>However, wallets such as <a href="https://trustwallet.com/">Trust
Wallet</a>, <a href="https://wallet.bitcoin.com/">Bitcoin.com
Wallet</a>, and <a href="https://litewallet.io/">Litewallet</a>, do not
support seed extension, and some can only accept 12-words seed phrases.
Therefore, it is best for one to actually study each wallets first,
before right away trust that they will support your backed up
wallets.</p>
<p>For myself, I have found some android wallets that actually implement
BIP39 properly, with standarized derivation paths. Those are <a
href="https://unstoppable.money/">Unstoppable Wallet</a>, and <a
href="https://airgap.it/">Airgap</a>. Unstoppable Wallet offers a large
selection of coins (mostly ERC20, BEP20, or BEP2 tokens), while Airgap
only offers a smaller selection of coins. However Airgap offers a more
advanced security, and the Airgap Vault can function as a hardware
wallet. Airgap is also very good on making new entropies, so far having
the best options to create our own entropies. Not that Airgap is the
best in making it, I just find it very interesting.</p>
<h2 id="backing-up-your-wallets">Backing Up Your Wallets</h2>
<p>It is worth noting, that, as mentioned before, BIP39 has its own
flaws: it is not true that all the user needs to restore their wallet is
<a href="https://walletsrecovery.org/">just their seed phrase</a>. Some
developers across the industry continue to build wallets that
either:</p>
<ul>
<li><p>Don’t implement BIP standard(s).</p></li>
<li><p>Implement a BIP standard, but inconsistently when compared with
other wallets.</p></li>
<li><p>Implement a BIP standard, but one that has not been widely
adopted (and perhaps only by them).</p></li>
<li><p>Don’t have clear documentation about their derivation paths,
backup and recovery processes.</p></li>
</ul>
<blockquote>
<p>Wallets come and go, information gets lost, and users are left with
tears. Responsible wallet developers document external recovery. Users
should not have to dig through the source code to figure out the
Derivation Paths or Redeem Scripts.</p>
<p><a
href="https://WalletsRecovery.org"><code>WalletsRecovery.org</code></a></p>
</blockquote>
<h3 id="know-your-wallet">Know Your Wallet</h3>
<p>Therefore, before settling on any wallet, you have to understand:</p>
<ol type="1">
<li><p>Whether they’re a deterministic wallet or not, and if yes, of
what type? Things to look out for is whether or not they have an option
to recover from seed phrases. If they do, they are most likely a
deterministic wallet. Most wallets that I have tested tend to derive
your addresses with BIP44, BIP49, or BIP84 standards. However you still
have to do your own research. <a
href="https://walletsrecovery.org/">This</a> is a good starting place
for you to read.</p></li>
<li><p>How they are going to derive your addresses from your seed
phrases? One thing to lookout for is their <code>derivation path</code>.
In the WalletsRecovery page, we could learn that via the
<strong>Supported Paths</strong> column on their tables. You are
required to study them yourselves, but as a general rule of thumbs:</p>
<ul>
<li><p>BIP44 standard tend to use <code>m/44'/0'/0'</code> + Custom,
with their generated addresses starts with a <code>1</code> and might
looks like <a
href="bitcoin:1PC9aZC4hNX2rmmrt7uHTfYAS3hRbph4UN"><code>1PC9aZC4hNX2rmmrt7uHTfYAS3hRbph4UN</code></a>
(Bitcoin donation addresses for the <a
href="https://www.fsf.org/about/ways-to-donate/">Free Software
Foundation</a>);</p></li>
<li><p>BIP49 standard tend to use <code>m/49'/0'/0'</code> + Custom, and
their addresses starts with a <code>3</code>, and might looks like
<code>3MaFikrngEw8YK6T5sxVoRmmYL3awgh846</code> (don’t send anything to
this address, I don’t know who owns it, and is generated only for
demonstrative purpose!);</p></li>
<li><p>BIP84 standard tend to use <code>m/84'/0'/0'</code> + Custom, and
their addresses starts with a <code>bc1</code>, and is not case
sensitive. Their addresses look like <a
href="bitcoin:bc1qm9ykrnyuknzsrrp6na6pysu7vzezf5yzlzj5hu"><code>bc1qm9ykrnyuknzsrrp6na6pysu7vzezf5yzlzj5hu</code></a>
(donation address for Xenomancy.id <code>:)</code> feel free to transfer
some funds there lol).</p></li>
</ul></li>
<li><p>Whether they are single address wallets or wallets that can
generate new receive addresses for each transactions. Some wallets like
Airgap Wallet, Trust Wallet, and many more wallets use a single address
for all transactions.</p>
<p>This is an expected behavior in some chains whose transaction is
processed in an account model, for example in Ethereum chain (ETH), or
in Binance Smart Chain (BSC). They do that because you need native funds
(ETH for ERC20 tokens, BNB for BEP20 tokens) in those addresses to move
tokens on that chain to another address.</p>
<p>Bitcoin and coins forked from Bitcoin like Litecoin and Dogecoin uses
UTXO model (UTXO stands for Unspent Transactions), where you are
expected to <a href="https://en.bitcoin.it/wiki/Address_reuse">never
reuse addresses</a>. Wallets that don’t reuse addresses include:
Unstoppable Wallet and BlueWallet.</p>
<p>It is worth noting that in Unstoppable Wallet, BEP2, BEP20, and ERC20
tokens (along with their native currencies ETH and BNB) uses only a
single address for reasons discussed before.</p></li>
</ol>
<p>To test them, you can use a randomly generated seed phrases from <a
href="https://iancoleman.io/bip39/#english">here</a>, and check their
generated addresses in any coins of your choosing, and compare those
addresses with addresses produced by your wallet. For safety reasons,
don’t actually use seed phrases generated online from that page as your
actual wallet and store funds within. If, for whatever reasons, you need
to use your seed phrase in that web tool, consider doing it offline by
downloading the tool <a
href="https://github.com/iancoleman/bip39/releases">from source</a> and
run them in an offline device.</p>
<p><strong>WARNING:</strong> YOU ARE RESPONSIBLE TO KEEP YOUR OWN SEED
PHRASE SAFE AND SECURE!</p>
<p>To test whether or not they reuse addresses, it’s a bit trickier, and
you might have to dig deeper on their documentations. I for example,
learned it only by trying to actually send and receive funds from those
wallet apps. That is when I learned that <a
href="https://community.trustwallet.com/t/removal-of-the-auto-change-address-feature/155623">Trust
Wallet does not generate new addresses after every transaction</a>. The
same is also <a
href="https://www.reddit.com/r/AirGap/comments/t1n5ct/issue_with_address_reuse/?utm_source=share&amp;utm_medium=web2x&amp;context=3">true
for Airgap Wallet</a>.</p>
<p>However, I discovered a rather curious behavior of Airgap Wallet.
Airgap Wallet did not generate new addresses after receiving a
transaction, however they can detect transactions of any receive and
change addresses derived from a seed phrase. How did I obtain other
addresses derived from that seed phrase in Airgap Wallet? I couldn’t,
but it is possible to look out other addresses from Airgap Vault, via a
feature known as <a
href="https://support.airgap.it/airgap-vault/address-explorer/">Address
Explorer</a>.</p>
<p>Some wallets didn’t directly provide information on how to export
wallets externally, but we can get hints on their feature lists. For
example, <a href="https://unstoppable.money/">Unstoppable Wallet</a>
explicitly described in their “Feature List” that they supports
<code>BIP 44/49/84/69</code>. <code>BIP 44/49/84</code> refers to
address standards discussed before.</p>
<h3 id="securing-your-secrets">Securing Your Secrets</h3>
<p>Then, after understanding how your wallet derive your addresses, and
what addresses they generate, the next step is to know how to secure
your seed phrase and seed extension. In short, <a
href="https://en.bitcoin.it/wiki/Storing_bitcoins">storing your
coins</a> boils down to backing up your <a
href="https://en.bitcoin.it/wiki/Seed_phrase">seed phrase</a>, and
storing them in a safe place, or on multiple safe places for backup.
According to the article, storing bitcoins consists of few independent
goals. All of which can be explored below:</p>
<ol type="1">
<li><p><strong>Protection against accidental loss.</strong> Modern day
deterministic wallets enabling us to secure our entire wallet just by
storing the seed phrase securely. Preferrably, in multiple copies stored
in multiple geographic locations for redundancy. That seed phrase can be
used to restore your wallet and recover all of your funds.<a href="#fn4"
class="footnote-ref" id="fnref4" role="doc-noteref"><sup>4</sup></a></p>
<p>However, since a seed phrase can store any large amount of money<a
href="#fn5" class="footnote-ref" id="fnref5"
role="doc-noteref"><sup>5</sup></a>, it makes little sense to keep them
unprotected. Therefore, you should also further secure your seed phrase
with a <a
href="https://en.bitcoin.it/wiki/Seed_phrase#Two-factor_seed_phrases">seed
extension</a>. Note that not all wallets support storing seed
extensions.</p>
<p>I think, it is also a common sense <a
href="https://support.airgap.it/airgap-vault/plausible-deniability/">not
to store your seed phrases along with their seed extension</a>. It will
literally be the same as storing non-protected seed phrases. Not storing
your seed extension at all is also dangerous, as losing access to that
is essentially the same as losing your seed phrase.</p>
<p>To conveniently store your seed phrases and seed extension, consider
storing them with <strong><em>pencil</em></strong> and
<strong><em>paper</em></strong>. Then that paper should be stored in the
dark while avoiding extreme heat and moisture. Some people might decide
to store them in <a
href="https://blog.lopp.net/metal-bitcoin-seed-storage-stress-test--part-ii-/">a
stamped or engraved metal</a>. Whatever your favorite way to store your
seed phrase, make sure to keep the paper (or metal plates) safe and
secret like cash or jewelry.</p></li>
<li><p><strong>Verification that the bitcoins are genuine,</strong> and
<strong>privacy and protection against spying.</strong> Storing your
seed phrases only store the seed required to derive private keys. To
learn about how many coins are stored within, we need a crypto wallet.
Ideally, we should have a private <a
href="https://en.bitcoin.it/wiki/Full_node">full node</a> that would
independently verify incoming and outgoing transactions from our
addresses. However it is impractical to do it nowadays, as per February
2022, the <a
href="https://fortunly.com/statistics/blockchain-statistics/#gref">size
of Bitcoin blockchain is 324 gigabytes</a>.</p>
<p>There are <a
href="https://en.bitcoin.it/wiki/Lightweight_node">lightweight
wallets</a> that do not store the entire blockchain history, and
confirms transactions by communicating with external full nodes.
Lightweight wallets have their weaknesses, especially as they do not
validate the rules of bitcoin, while sending addresses to third parties
and receive wallet balance and history, while also skipping several
security steps that can make their users vulnerable.</p>
<p>If you value privacy, consider setting up a full node. However, if
you value speed and minimal storage use, consider using light wallets.
Practically all mobile wallets are lightweight wallets, simply because
it is impractical to store the entire blockchain history in a mobile
device.</p></li>
<li><p><strong>Protection against theft.</strong> As discussed before,
anyone with access of your keys, has access to your coins. The same is
true with seed phrases and seed extension, as they provides a mean to
derive all of your keys. It is not even about a physical possession of
the seed phrases and seed extension, but also extends on the security of
devices you use to manage your wallets.</p>
<p>Therefore, you have to ensure that the wallet software running on
your device, your device’s operating system and its hardware are safe
and secure. Also worth noting that generally desktop computers <a
href="https://support.airgap.it/airgap-vault/supported-devices">lack
specific mobile features</a> such as Secure Storage and Biometrics to
secure your secrets. Generally, <a
href="https://latesthackingnews.com/2018/08/19/why-linux-is-more-secure-that-windows-but-still-not-as-malware-free-as-you-might-think/#:~:text=Linux%20does%20not%20easily%20deliver,installed%20and%20run%20as%20root.">a
linux-based system is less vulnerable to malwares</a>.<a href="#fn6"
class="footnote-ref" id="fnref6"
role="doc-noteref"><sup>6</sup></a></p></li>
<li><p><strong>Easy access for spending or moving bitcoins.</strong> It
really depends on whether or not you need to move your coins often or
not. If you need to move your coins around often, consider having a
separate wallet of which a reasonable amount of funds that are expected
to be moved around are stored within. The rest of your funds that don’t
need to be moved around that often should be stored in a more secure
wallet. Consider setting up a <a
href="https://en.bitcoin.it/wiki/Cold_storage">cold storage device</a>,
or <a href="https://en.bitcoin.it/wiki/Hardware_wallet">having a
hardware wallet</a> that would secure your private keys and would not
expose them to your hot devices.<a href="#fn7" class="footnote-ref"
id="fnref7" role="doc-noteref"><sup>7</sup></a></p>
<p>A compromise to all above that I find useful is to repurpose an old
android device as your own airgapped hardware wallet, an approach that
is done by <a href="https://airgap.it">Airgap</a> with their dual app
model.</p></li>
</ol>
<p>As a summary, <a
href="https://en.bitcoin.it/wiki/Storing_bitcoins">this page</a>
cleverly explains as follows:</p>
<blockquote>
<p>bitcoin wallets should be backed up by writing down their <a
href="https://en.bitcoin.it/wiki/Seed_phrase">seed phrase</a>, this
phrase must be kept safe and secret, and when sending or receiving
transactions the wallet software should obtain information about the
bitcoin network from your own <a
href="https://en.bitcoin.it/wiki/Full_node">full node</a>.</p>
</blockquote>
<p>However, it is worth noting that we should also understand the nature
of our wallets of choosing (see Know Your Wallet section).</p>
<section id="wallet-software-reviews" class="content-table">
<h2>Wallet Software Reviews</h2>
<p>There are many wallets that I have tried. Like discussed above, not
every wallets supports all features, so expect some trade-offs on
various wallets. Therefore, we should also consider features and
standards they support. Below is a list of wallets I find worth
mentioning.</p>
<h3 id="airgap-wallet-and-airgap-vault">Airgap Wallet and Airgap
Vault</h3>
<table>
<caption><a href="https://airgap.it/">Airgap homepage</a>. Source for <a
href="https://github.com/airgap-it/airgap-vault">Airgap Vault</a> and <a
href="https://github.com/airgap-it/airgap-wallet">Airgap Wallet</a> on
github.</caption>
<colgroup>
<col style="width: 25%" />
<col style="width: 21%" />
<col style="width: 53%" />
</colgroup>
<thead>
<tr class="header">
<th style="text-align: left;">Criterion</th>
<th style="text-align: left;">Status</th>
<th style="text-align: left;">Note</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: left;">Platform</td>
<td style="text-align: left;">Android</td>
<td style="text-align: left;">-</td>
</tr>
<tr class="even">
<td style="text-align: left;">Full/Light Node</td>
<td style="text-align: left;">Light</td>
<td style="text-align: left;">A mobile wallet.</td>
</tr>
<tr class="odd">
<td style="text-align: left;">Offline</td>
<td style="text-align: left;">True</td>
<td style="text-align: left;">Airgap Vault is offline, while Airgap
Wallet is online. They communicates either via app switching (single
device mode), or QR codes (two devices mode).</td>
</tr>
<tr class="even">
<td style="text-align: left;">Seed phrase</td>
<td style="text-align: left;">True</td>
<td style="text-align: left;">HD Wallet</td>
</tr>
<tr class="odd">
<td style="text-align: left;">Seed extension</td>
<td style="text-align: left;">True</td>
<td style="text-align: left;">Cannot be included when importing secrets.
However can be used when <a
href="https://support.airgap.it/airgap-vault/plausible-deniability/">generating
accounts</a>.</td>
</tr>
<tr class="even">
<td style="text-align: left;">BIP44</td>
<td style="text-align: left;">True</td>
<td style="text-align: left;">-</td>
</tr>
<tr class="odd">
<td style="text-align: left;">BIP49</td>
<td style="text-align: left;">False</td>
<td style="text-align: left;">Not available.</td>
</tr>
<tr class="even">
<td style="text-align: left;">BIP84</td>
<td style="text-align: left;">True</td>
<td style="text-align: left;">-</td>
</tr>
<tr class="odd">
<td style="text-align: left;">Address Reuse</td>
<td style="text-align: left;">True</td>
<td style="text-align: left;">However we can manually find derived
addresses in Airgap Vault’s <a
href="https://support.airgap.it/airgap-vault/address-explorer/">Address
Explorer</a>. Airgap Wallet will automatically detect incoming
transactions to those addresses.</td>
</tr>
<tr class="even">
<td style="text-align: left;">Derive New Addresses</td>
<td style="text-align: left;">True</td>
<td style="text-align: left;">This part is only for ERC20 addresses.
Airgap can only generate new addresses of the same seed phrase by
applying a new seed extension.</td>
</tr>
<tr class="odd">
<td style="text-align: left;">DApp Support</td>
<td style="text-align: left;">Not tested</td>
<td style="text-align: left;"><a
href="https://support.airgap.it/airgap-wallet/introduction/">They can
use WalletConnect</a>.</td>
</tr>
</tbody>
</table>
<h3 id="bitcoin-wallet">Bitcoin Wallet</h3>
<table>
<caption>Source of <a
href="https://github.com/bitcoin-wallet/bitcoin-wallet/">Bitcoin
Wallet</a> on github.</caption>
<colgroup>
<col style="width: 25%" />
<col style="width: 21%" />
<col style="width: 53%" />
</colgroup>
<thead>
<tr class="header">
<th style="text-align: left;">Criterion</th>
<th style="text-align: left;">Status</th>
<th style="text-align: left;">Note</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: left;">Platform</td>
<td style="text-align: left;">Android</td>
<td style="text-align: left;">-</td>
</tr>
<tr class="even">
<td style="text-align: left;">Full/Light Node</td>
<td style="text-align: left;">Light</td>
<td style="text-align: left;">-</td>
</tr>
<tr class="odd">
<td style="text-align: left;">Offline</td>
<td style="text-align: left;">Not tested</td>
<td style="text-align: left;">-</td>
</tr>
<tr class="even">
<td style="text-align: left;">Seed phrase</td>
<td style="text-align: left;">False</td>
<td style="text-align: left;">Keypairs are generated, and saved as
wallet backups.</td>
</tr>
<tr class="odd">
<td style="text-align: left;">Seed extension</td>
<td style="text-align: left;">False</td>
<td style="text-align: left;">-</td>
</tr>
<tr class="even">
<td style="text-align: left;">BIP44</td>
<td style="text-align: left;">False</td>
<td style="text-align: left;">-</td>
</tr>
<tr class="odd">
<td style="text-align: left;">BIP49</td>
<td style="text-align: left;">False</td>
<td style="text-align: left;">-</td>
</tr>
<tr class="even">
<td style="text-align: left;">BIP84</td>
<td style="text-align: left;">True</td>
<td style="text-align: left;">Based on its receive address.</td>
</tr>
<tr class="odd">
<td style="text-align: left;">Address Reuse</td>
<td style="text-align: left;">False</td>
<td style="text-align: left;">Randomly generated keypairs. Switches to a
new addresses for every transactions.</td>
</tr>
<tr class="even">
<td style="text-align: left;">Derive New Addresses</td>
<td style="text-align: left;">-</td>
<td style="text-align: left;">This is relevant only on Account model
blockchains.</td>
</tr>
<tr class="odd">
<td style="text-align: left;">DApp Support</td>
<td style="text-align: left;">False</td>
<td style="text-align: left;">-</td>
</tr>
</tbody>
</table>
<h3 id="bitcoin.com-wallet">Bitcoin.com Wallet</h3>
<table>
<caption>Bitcoin.com <a href="https://wallet.bitcoin.com/">homepage</a>.
<a href="https://github.com/Bitcoin-com/Wallet">Source</a> on
github.</caption>
<colgroup>
<col style="width: 25%" />
<col style="width: 21%" />
<col style="width: 53%" />
</colgroup>
<thead>
<tr class="header">
<th style="text-align: left;">Criterion</th>
<th style="text-align: left;">Status</th>
<th style="text-align: left;">Note</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: left;">Platform</td>
<td style="text-align: left;">Android</td>
<td style="text-align: left;">-</td>
</tr>
<tr class="even">
<td style="text-align: left;">Full/Light Node</td>
<td style="text-align: left;">Light</td>
<td style="text-align: left;">-</td>
</tr>
<tr class="odd">
<td style="text-align: left;">Offline</td>
<td style="text-align: left;">False</td>
<td style="text-align: left;">Requires internet to check balance.</td>
</tr>
<tr class="even">
<td style="text-align: left;">Seed phrase</td>
<td style="text-align: left;">True</td>
<td style="text-align: left;">Only accepts 12-word mnemonics.</td>
</tr>
<tr class="odd">
<td style="text-align: left;">Seed extension</td>
<td style="text-align: left;">False</td>
<td style="text-align: left;">Not supported.</td>
</tr>
<tr class="even">
<td style="text-align: left;">BIP44</td>
<td style="text-align: left;">True</td>
<td style="text-align: left;">-</td>
</tr>
<tr class="odd">
<td style="text-align: left;">BIP49</td>
<td style="text-align: left;">False</td>
<td style="text-align: left;">-</td>
</tr>
<tr class="even">
<td style="text-align: left;">BIP84</td>
<td style="text-align: left;">False</td>
<td style="text-align: left;">-</td>
</tr>
<tr class="odd">
<td style="text-align: left;">Address Reuse</td>
<td style="text-align: left;">False</td>
<td style="text-align: left;">Can generate new addresses.</td>
</tr>
<tr class="even">
<td style="text-align: left;">Derive New Addresses</td>
<td style="text-align: left;">Not tested</td>
<td style="text-align: left;">I didn’t check its ethereum addresses very
closely.</td>
</tr>
<tr class="odd">
<td style="text-align: left;">DApp Support</td>
<td style="text-align: left;">Not tested</td>
<td style="text-align: left;">Apparently can connect to <a
href="https://github.com/bitcoin-portal/bitcoin-wallet-releases/releases/tag/v7.13.3">DApps
via WalletConnect</a>.</td>
</tr>
</tbody>
</table>
<h3 id="electrum">Electrum</h3>
<table>
<caption><a href="https://electrum.org/#home">Electrum</a> website. <a
href="https://github.com/spesmilo/electrum/">Electrum</a>
source.</caption>
<colgroup>
<col style="width: 25%" />
<col style="width: 21%" />
<col style="width: 53%" />
</colgroup>
<thead>
<tr class="header">
<th style="text-align: left;">Criterion</th>
<th style="text-align: left;">Status</th>
<th style="text-align: left;">Note</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: left;">Platform</td>
<td style="text-align: left;">Desktop</td>
<td style="text-align: left;">-</td>
</tr>
<tr class="even">
<td style="text-align: left;">Full/Light Node</td>
<td style="text-align: left;">Both</td>
<td style="text-align: left;">You can run it as a full node or a light
client.</td>
</tr>
<tr class="odd">
<td style="text-align: left;">Offline</td>
<td style="text-align: left;">True</td>
<td style="text-align: left;">Can be used in an offline, airgapped
device as a cold wallet.</td>
</tr>
<tr class="even">
<td style="text-align: left;">Seed phrase</td>
<td style="text-align: left;">True</td>
<td style="text-align: left;">Have <a
href="https://electrum.readthedocs.io/en/latest/seedphrase.html">their
own standard</a>.</td>
</tr>
<tr class="odd">
<td style="text-align: left;">Seed extension</td>
<td style="text-align: left;">True</td>
<td style="text-align: left;">-</td>
</tr>
<tr class="even">
<td style="text-align: left;">BIP44</td>
<td style="text-align: left;">True</td>
<td style="text-align: left;">-</td>
</tr>
<tr class="odd">
<td style="text-align: left;">BIP49</td>
<td style="text-align: left;">True</td>
<td style="text-align: left;">-</td>
</tr>
<tr class="even">
<td style="text-align: left;">BIP84</td>
<td style="text-align: left;">True</td>
<td style="text-align: left;">-</td>
</tr>
<tr class="odd">
<td style="text-align: left;">Address Reuse</td>
<td style="text-align: left;">True</td>
<td style="text-align: left;">Can always generate new addresses. It is
up to the user.</td>
</tr>
<tr class="even">
<td style="text-align: left;">Derive New Addresses</td>
<td style="text-align: left;">-</td>
<td style="text-align: left;">This is relevant only on Account model
blockchains.</td>
</tr>
<tr class="odd">
<td style="text-align: left;">DApp Support</td>
<td style="text-align: left;">False</td>
<td style="text-align: left;">-</td>
</tr>
</tbody>
</table>
<h3 id="myetherwallet">MyEtherWallet</h3>
<table>
<caption><a href="https://www.myetherwallet.com/">MyEtherWallet</a>
website. <a
href="https://github.com/MyEtherWallet/MyEtherWallet">MyEtherWallet</a>
source.</caption>
<colgroup>
<col style="width: 25%" />
<col style="width: 21%" />
<col style="width: 53%" />
</colgroup>
<thead>
<tr class="header">
<th style="text-align: left;">Criterion</th>
<th style="text-align: left;">Status</th>
<th style="text-align: left;">Note</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: left;">Platform</td>
<td style="text-align: left;">Android</td>
<td style="text-align: left;">-</td>
</tr>
<tr class="even">
<td style="text-align: left;">Full/Light Node</td>
<td style="text-align: left;">Light</td>
<td style="text-align: left;">-</td>
</tr>
<tr class="odd">
<td style="text-align: left;">Offline</td>
<td style="text-align: left;">True</td>
<td style="text-align: left;">Can be used <a
href="https://help.myetherwallet.com/en/articles/5380611-using-mew-offline-cold-storage">offline</a>.</td>
</tr>
<tr class="even">
<td style="text-align: left;">Seed phrase</td>
<td style="text-align: left;">True</td>
<td style="text-align: left;">Apparently accepts BIP39 seed
phrases.</td>
</tr>
<tr class="odd">
<td style="text-align: left;">Seed extension</td>
<td style="text-align: left;">False</td>
<td style="text-align: left;">-</td>
</tr>
<tr class="even">
<td style="text-align: left;">BIP44</td>
<td style="text-align: left;">False</td>
<td style="text-align: left;">-</td>
</tr>
<tr class="odd">
<td style="text-align: left;">BIP49</td>
<td style="text-align: left;">False</td>
<td style="text-align: left;">-</td>
</tr>
<tr class="even">
<td style="text-align: left;">BIP84</td>
<td style="text-align: left;">False</td>
<td style="text-align: left;">-</td>
</tr>
<tr class="odd">
<td style="text-align: left;">Address Reuse</td>
<td style="text-align: left;">True</td>
<td style="text-align: left;">It is an ETH and ERC20 wallet with their
Account model. It reuses addresses by design.</td>
</tr>
<tr class="even">
<td style="text-align: left;">Derive New Addresses</td>
<td style="text-align: left;">True</td>
<td style="text-align: left;">Can generate new addresses derived from
the same seed phrase.</td>
</tr>
<tr class="odd">
<td style="text-align: left;">DApp Support</td>
<td style="text-align: left;">True</td>
<td style="text-align: left;">With MEWConnect.</td>
</tr>
</tbody>
</table>
<h3 id="trustwallet">TrustWallet</h3>
<table>
<caption><a href="https://trustwallet.com/">Trustwallet</a> website. <a
href="https://github.com/trustwallet/wallet-core">Trustwallet</a>
source.</caption>
<colgroup>
<col style="width: 25%" />
<col style="width: 21%" />
<col style="width: 53%" />
</colgroup>
<thead>
<tr class="header">
<th style="text-align: left;">Criterion</th>
<th style="text-align: left;">Status</th>
<th style="text-align: left;">Note</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: left;">Platform</td>
<td style="text-align: left;">Android</td>
<td style="text-align: left;">-</td>
</tr>
<tr class="even">
<td style="text-align: left;">Full/Light Node</td>
<td style="text-align: left;">Light</td>
<td style="text-align: left;">-</td>
</tr>
<tr class="odd">
<td style="text-align: left;">Offline</td>
<td style="text-align: left;">False</td>
<td style="text-align: left;">-</td>
</tr>
<tr class="even">
<td style="text-align: left;">Seed phrase</td>
<td style="text-align: left;">True</td>
<td style="text-align: left;">12 to 24 words mnemonics can be
imported.</td>
</tr>
<tr class="odd">
<td style="text-align: left;">Seed extension</td>
<td style="text-align: left;">False</td>
<td style="text-align: left;">-</td>
</tr>
<tr class="even">
<td style="text-align: left;">BIP44</td>
<td style="text-align: left;">False</td>
<td style="text-align: left;">-</td>
</tr>
<tr class="odd">
<td style="text-align: left;">BIP49</td>
<td style="text-align: left;">False</td>
<td style="text-align: left;">-</td>
</tr>
<tr class="even">
<td style="text-align: left;">BIP84</td>
<td style="text-align: left;">True</td>
<td style="text-align: left;">Documentations can be found on its <a
href="https://community.trustwallet.com/c/helpcenter/migration/15">support
center</a> and on <a
href="https://walletsrecovery.org/">WalletsRecovery</a> page.</td>
</tr>
<tr class="odd">
<td style="text-align: left;">Address Reuse</td>
<td style="text-align: left;">True</td>
<td style="text-align: left;">Ability to generate a new address is <a
href="https://community.trustwallet.com/t/removal-of-the-auto-change-address-feature/155623">removed</a>.</td>
</tr>
<tr class="even">
<td style="text-align: left;">Derive New Addresses</td>
<td style="text-align: left;">False</td>
<td style="text-align: left;">See above.</td>
</tr>
<tr class="odd">
<td style="text-align: left;">DApp Support</td>
<td style="text-align: left;">True</td>
<td style="text-align: left;">WalletConnect is available. Can select
addresses and chains.</td>
</tr>
<tr class="even">
<td style="text-align: left;">Other notes</td>
<td style="text-align: left;">-</td>
<td style="text-align: left;">Trustwallet can be used to stake crypto in
several chains. For example BNB in Beacon Chain, TRX, and LUNA. It also
has a built-in decentralized exchange. When making an open order there,
it would also be visible in <a
href="https://www.bnbchain.world/en/trade/BTCB-1DE_BUSD-BD1">Binance
DEX</a>.</td>
</tr>
</tbody>
</table>
<h3 id="unstoppable-wallet">Unstoppable Wallet</h3>
<table>
<caption><a href="https://unstoppable.money/">Unstoppable wallet</a>
website. <a
href="https://github.com/horizontalsystems/unstoppable-wallet-android/">Unstoppable
wallet</a> source code.</caption>
<colgroup>
<col style="width: 25%" />
<col style="width: 21%" />
<col style="width: 53%" />
</colgroup>
<thead>
<tr class="header">
<th style="text-align: left;">Criterion</th>
<th style="text-align: left;">Status</th>
<th style="text-align: left;">Note</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: left;">Platform</td>
<td style="text-align: left;">Android</td>
<td style="text-align: left;">-</td>
</tr>
<tr class="even">
<td style="text-align: left;">Full/Light Node</td>
<td style="text-align: left;">Light</td>
<td style="text-align: left;">-</td>
</tr>
<tr class="odd">
<td style="text-align: left;">Offline</td>
<td style="text-align: left;">False</td>
<td style="text-align: left;">Must connect to the internet to check
balances and interact with DApps.</td>
</tr>
<tr class="even">
<td style="text-align: left;">Seed phrase</td>
<td style="text-align: left;">True</td>
<td style="text-align: left;">Accepts BIP39 mnemonics, between 12 to 24
words.</td>
</tr>
<tr class="odd">
<td style="text-align: left;">Seed extension</td>
<td style="text-align: left;">True</td>
<td style="text-align: left;">Accepts BIP39 passphrase.</td>
</tr>
<tr class="even">
<td style="text-align: left;">BIP44</td>
<td style="text-align: left;">True</td>
<td style="text-align: left;">Can be selected.</td>
</tr>
<tr class="odd">
<td style="text-align: left;">BIP49</td>
<td style="text-align: left;">True</td>
<td style="text-align: left;">Can be selected.</td>
</tr>
<tr class="even">
<td style="text-align: left;">BIP84</td>
<td style="text-align: left;">True</td>
<td style="text-align: left;">Can be selected.</td>
</tr>
<tr class="odd">
<td style="text-align: left;">Address Reuse</td>
<td style="text-align: left;">False</td>
<td style="text-align: left;">New fresh addresses are displayed after
every transactions.</td>
</tr>
<tr class="even">
<td style="text-align: left;">Derive New Addresses</td>
<td style="text-align: left;">False</td>
<td style="text-align: left;">For BEP2, BEP20, and ERC20 tokens, only a
single address per seed phrase and seed extension combinations is
used.</td>
</tr>
<tr class="odd">
<td style="text-align: left;">DApp Support</td>
<td style="text-align: left;">True</td>
<td style="text-align: left;">WalletConnect, but only on BEP20 and ERC20
chain.</td>
</tr>
</tbody>
</table>
</section>
<aside id="footnotes" class="footnotes footnotes-end-of-document"
role="doc-endnotes">
<hr />
<ol>
<li id="fn1"><p>This is the year of BIP-0032 introduction, as can be
seen here:
https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki<a
href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn2"><p>Also known as <em>mnemonic phrase</em>, <em>seed
recovery phrase</em>, or <em>backup seed phrase</em><a href="#fnref2"
class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn3"><p>Also known as <em>extension word</em>,
<em>passphrase</em>, or <em>13th/25th word</em>.<a href="#fnref3"
class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn4"><p>Likewise, anyone that has access to your seed phrase,
can literally own all of the funds contained within.<a href="#fnref4"
class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn5"><p>You can, for example, have funds enough to purchase an
entire building laying unprotected as a page of a seed phrase backup.<a
href="#fnref5" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn6"><p>Note that it does not in any way intended to mean that
linux-based system is invulnerable.<a href="#fnref6"
class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn7"><p>Devices that are connected to the internet. In other
words, an online device.<a href="#fnref7" class="footnote-back"
role="doc-backlink">↩︎</a></p></li>
</ol>
</aside>]]></description>
                <author><![CDATA[Hendrik Lie]]></author>
                <guid>9993c18b-ffa2-4bd2-960c-d0cc404a853e</guid>
                <pubDate>Wed, 08 May 2024 11:29:41 +0700</pubDate>
            </item>
                    <item>
                <title><![CDATA[BIP85 Use Case]]></title>
                <link>https://liecorp.id/blog/bip85-use-case</link>
                <description><![CDATA[<div class="disclaimer">
<blockquote>
<p>This page is originally written for and is published on Static Blog
Alpha at 11 August 2022. You can access the original version <a
href="https://arweave.net/f3cjnQSAtpETB06kzzafmLrAk7n2dQrFbO9VMESu244/pages/SBA-008.html">here</a>.</p>
</blockquote>
</div>
<p><a
href="https://github.com/bitcoin/bips/blob/master/bip-0085.mediawiki">BIP85</a>
opens up the possibility of backing up only one seed, and use it to
derive any future seeds. The operation is also just a one-way operation,
any derived seeds cannot be used to find out its parent seed. Therefore,
it simplifies backup processes a lot.</p>
<p>Our backup could, for example, only stores our seed phrase. Then, for
any new derived seeds, we can just store the derived seed’s index. A
backup of our parent seed, is then effectively also a backup of all of
our keys.</p>
<p>There has been some uses in crypto wallet technologies. One of
hardware wallet vendors that implement BIP85 is <a
href="https://coldcard.com/docs/bip85">coldcard</a>, at least as far as
I discovered. One software wallet that does support deriving new seeds
based on BIP85 that I know of is <a
href="https://support.airgap.it/features/bip85/">Airgap Vault</a>.</p>
<p>In some cases, maybe you don’t want to rely on coldcard firmware or
Airgap Vault to derive your child seeds. Therefore, you can also derive
your seeds with the use of <a href="https://iancoleman.io/bip39/">Ian
Coleman’s Mnemonic Code Converter</a>. Again, I am not sure how many
times do I need to emphasize it, but:</p>
<p><strong>EXPOSING YOUR SEED PHRASE TO A NON-AIRGAPPED DEVICE MIGHT
COMPROMISE YOUR SEED PHRASE!</strong><a href="#fn1" class="footnote-ref"
id="fnref1" role="doc-noteref"><sup>1</sup></a></p>
<p><em>Airplane mode</em> is available to most modern devices. Also,
<em>incognito mode</em> that would delete all history and caches after
the tab is closed is not hard to find in all good modern browsers, be it
in mobile or desktop.</p>
<p>To use the Mnemonic Code Converter offline, you can either:</p>
<ul>
<li><p>load the page first in an incognito tab, then turn on airplane
mode and use the tool;<a href="#fn2" class="footnote-ref" id="fnref2"
role="doc-noteref"><sup>2</sup></a> or</p></li>
<li><p>download the latest version of the tool’s <a
href="https://github.com/iancoleman/bip39/releases">releases</a>, then
copy it to an offline device<a href="#fn3" class="footnote-ref"
id="fnref3" role="doc-noteref"><sup>3</sup></a>, and run the tool in an
incognito mode.</p></li>
</ul>
<p>However, it is worth noting that, unlike that advertised in the
linked coldcard documentation, it is not true that all we need to back
up our wallet is just our parent seed and its BIP85 derivation index.
According to the <a
href="https://github.com/bitcoin/bips/blob/master/bip-0085.mediawiki">BIP85
specification</a>, the derived entropy is different according to its
application number. In case of deriving child BIP39 seeds, the
derivation path format is:
<code>m/83696968'/39'/{language}'/{words}'/{index}'</code></p>
<p>We also discovered that there are at least three factors to consider:
<code>language</code>, <code>words</code>, and <code>index</code>. If
you mostly use English-language wallets, then the language index would
be <code>0'</code>.<a href="#fn4" class="footnote-ref" id="fnref4"
role="doc-noteref"><sup>4</sup></a> For <code>words</code>, it refers to
the number of words in your seed phrase, that could either be 12-words,
18-words, or 24-words. Meanwhile, <code>index</code> part is trivial:
it’s just the derivation index of your mnemonics.</p>
<p>For example, if you want to derive a 12-word seed phrase from your
seed phrase with <code>index = 0</code>, the derivation path would be:
<code>m/83696968'/39'/0'/12'/0'</code>. For <code>index = 1</code>, the
derivation path would be: <code>m/83696968'/39'/0'/12'/1'</code>.
Meanwhile, if you want to derive a 24-word seed phrase instead, they
would be: <code>m/83696968'/39'/0'/24'/0'</code> and
<code>m/83696968'/39'/0'/24'/1'</code> instead.</p>
<p>Therefore, assuming English-language seed phrases,<a href="#fn5"
class="footnote-ref" id="fnref5" role="doc-noteref"><sup>5</sup></a> all
you need to write down to back up all of your seed phrases is the
following:</p>
<ol type="1">
<li><p>Your parent seed phrase. It encodes your original entropy, and
without it, no entropy can be derived. Additionally, you also need your
seed extension if you have one.<a href="#fn6" class="footnote-ref"
id="fnref6" role="doc-noteref"><sup>6</sup></a></p></li>
<li><p>The size of your derived seed phrase. It could be a 12-words,
18-words, or 24-words seed phrase.</p></li>
<li><p>Your child seed phrase index.</p></li>
</ol>
<p>Assuming you just back up your seed phrases, without backing point 2
or point 3, there would be almost unlimited amount of combinations that
you have to look out for.<a href="#fn7" class="footnote-ref" id="fnref7"
role="doc-noteref"><sup>7</sup></a> In coldcard, even though the index
runs just from <code>0000</code> to <code>9999</code> with marketedly,
“there are only 10,000 possible choices”, is not entirely correct. Given
that each derived seed can either be in a 12, 18, or 24 words format,
the actual number of possible choices are <code>30,000</code>.<a
href="#fn8" class="footnote-ref" id="fnref8"
role="doc-noteref"><sup>8</sup></a> For Airgap Vault, there can only be
30 possible choices though, as the app can only derive child seed
phrases from index 0 to index 9.</p>
<p>Given all of the above as our considerations, and that assuming only
English seed phrases, also that every tool we are going to use
implements BIP85 standard properly, we can conveniently record our
derived seed phrases by storing these four values:
<code>parent seed phrase</code>, <code>parent seed extension</code>,
<code>words</code>, and <code>index</code>.</p>
<aside id="footnotes" class="footnotes footnotes-end-of-document"
role="doc-endnotes">
<hr />
<ol>
<li id="fn1"><p>Of course, this might be important for privacy and
security conscious folks. Still, it is wise, and wouldn’t hurt, to run
the tool while offline only.<a href="#fnref1" class="footnote-back"
role="doc-backlink">↩︎</a></p></li>
<li id="fn2"><p>Be sensible. Before turning off your airplane mode,
don’t forget to close the incognito tab first. Otherwise it would be the
same as using the tool while online :)<a href="#fnref2"
class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn3"><p>or just run it on the same device but offline and in an
incognito tab.<a href="#fnref3" class="footnote-back"
role="doc-backlink">↩︎</a></p></li>
<li id="fn4"><p>I couldn’t find any wallet in the market that uses other
language for creating or importing seed phrases. Although it is highly
unlikely, if you do use or have a seed that is not in english language,
consult the BIP85 specification page for its language index, and your
wallet’s documentations.<a href="#fnref4" class="footnote-back"
role="doc-backlink">↩︎</a></p></li>
<li id="fn5"><p>Also optimistically assumes that your preferred wallet
or tool follows BIP85 specification very closely.<a href="#fnref5"
class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn6"><p>So far, I know only Ian Coleman’s Mnemonic Code
Converter and Airgap Vault implement this feature conveniently. Coldcard
apparently allows deriving entropy from a passphrase protected seeds,
but they discouraged it.<a href="#fnref6" class="footnote-back"
role="doc-backlink">↩︎</a></p></li>
<li id="fn7"><p>Ian Coleman’s Mnemonic Code Converter tool seems to have
no practical limit on index number. However, for curious folks, I’ve
tested the tool’s upper limit, which seems to be capable of deriving
child mnemonics from index <code>0</code> to index
<code>2147483647</code>, which means there are 2^31 possible index
numbers. Though not impossible, finding the right random index number
you use by trying one by one is impractical.<a href="#fnref7"
class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn8"><p>Although to be fair, it is still in the same order of
magnitude as the originally quoted 10,000 possible choices. It will be a
major inconvenience, but it is not impossible.<a href="#fnref8"
class="footnote-back" role="doc-backlink">↩︎</a></p></li>
</ol>
</aside>]]></description>
                <author><![CDATA[Hendrik Lie]]></author>
                <guid>dd01bc9f-6d4d-4fb1-8c12-b5296339a444</guid>
                <pubDate>Wed, 08 May 2024 11:21:49 +0700</pubDate>
            </item>
            </channel>
</rss>
