Jekyll2022-09-30T21:29:47+00:00https://assafmo.github.io/feed.xmlAssaf MoramiThings I have learned over time. :-)
Assaf MoramiHosting your own PPA repository on GitHub2019-05-02T00:00:00+00:002019-05-02T00:00:00+00:00https://assafmo.github.io/2019/05/02/ppa-repo-hosted-on-github<p>Publishing your own Debian packages and hosting it on a GitHub repo is pretty easy. This is a quick HowTo.</p>
<h3 id="a-ppa-repo-can-be-as-simple-as-one-directory">A PPA repo can be as simple as one directory</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.
└── my_ppa
├── my_list_file.list
├── InRelease
├── KEY.gpg
├── Packages
├── Packages.gz
├── Release
├── Release.gpg
├── package-a_0.0.1_amd64.deb
├── package-a_0.0.2_amd64.deb
├── package-b_0.1.0_amd64.deb
├── package-b_0.1.1_amd64.deb
├── ...
└── package-z_1.0.0_amd64.deb
</code></pre></div></div>
<p>A working example can be found in <a href="https://github.com/assafmo/ppa">https://github.com/assafmo/ppa</a>.</p>
<p>You can name <code class="language-plaintext highlighter-rouge">my_ppa</code> and <code class="language-plaintext highlighter-rouge">my_list_file.list</code> whatever you like. I used those names because it’s hard to name things.</p>
<p>Also don’t forget to replace <code class="language-plaintext highlighter-rouge">${GITHUB_USERNAME}</code> with your GitHub user name and <code class="language-plaintext highlighter-rouge">${EMAIL}</code> with your email address.</p>
<h3 id="0-creating-a-github-repo-with-your-deb-packages">0. Creating a GitHub repo with your deb packages</h3>
<p><a href="https://github.com/new">Create a GitHub repo</a>. We’ll call it <code class="language-plaintext highlighter-rouge">my_ppa</code>. Then go to <code class="language-plaintext highlighter-rouge">https://github.com/${GITHUB_USERNAME}/my_ppa/settings</code>, and under <code class="language-plaintext highlighter-rouge">GitHub Pages</code> select <code class="language-plaintext highlighter-rouge">Source</code> to be <code class="language-plaintext highlighter-rouge">master branch</code>.</p>
<p>Any HTTP server will work just fine, but GitHub pages is free, easy and fast.</p>
<p>Now clone the repo and put all of your debian packages inside:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone <span class="s2">"[email protected]:</span><span class="k">${</span><span class="nv">GITHUB_USERNAME</span><span class="k">}</span><span class="s2">/my_ppa.git"</span>
<span class="nb">cd </span>my_ppa
<span class="nb">cp</span> /path/to/my/package-a_0.0.1_amd64.deb <span class="nb">.</span>
</code></pre></div></div>
<h3 id="1-creating-a-gpg-key">1. Creating a GPG key</h3>
<p>Install <code class="language-plaintext highlighter-rouge">gpg</code> and create a new key:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt <span class="nb">install </span>gnupg
gpg <span class="nt">--full-gen-key</span>
</code></pre></div></div>
<p>Use RSA:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Please <span class="k">select </span>what kind of key you want:
<span class="o">(</span>1<span class="o">)</span> RSA and RSA <span class="o">(</span>default<span class="o">)</span>
<span class="o">(</span>2<span class="o">)</span> DSA and Elgamal
<span class="o">(</span>3<span class="o">)</span> DSA <span class="o">(</span>sign only<span class="o">)</span>
<span class="o">(</span>4<span class="o">)</span> RSA <span class="o">(</span>sign only<span class="o">)</span>
Your selection? 1
</code></pre></div></div>
<p>RSA with 4096 bits:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>RSA keys may be between 1024 and 4096 bits long.
What keysize <span class="k">do </span>you want? <span class="o">(</span>3072<span class="o">)</span> 4096
</code></pre></div></div>
<p>Key should be valid forever:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Please specify how long the key should be valid.
0 <span class="o">=</span> key does not expire
<n> <span class="o">=</span> key expires <span class="k">in </span>n days
<n>w <span class="o">=</span> key expires <span class="k">in </span>n weeks
<n>m <span class="o">=</span> key expires <span class="k">in </span>n months
<n>y <span class="o">=</span> key expires <span class="k">in </span>n years
Key is valid <span class="k">for</span>? <span class="o">(</span>0<span class="o">)</span> 0
Key does not expire at all
Is this correct? <span class="o">(</span>y/N<span class="o">)</span> y
</code></pre></div></div>
<p>Enter your name and email:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Real name: My Name
Email address: ${EMAIL}
Comment:
You selected this USER-ID:
"My Name <[email protected]>"
Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O
</code></pre></div></div>
<p>At this point the <code class="language-plaintext highlighter-rouge">gpg</code> command will start to create your key and will ask for a passphrase for extra protection. I like to leave it blank so when I sign things with my key it won’t promp for the passphrase each time.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
gpg: key B58FBB4C23247554 marked as ultimately trusted
gpg: directory '/home/assafmo/.gnupg/openpgp-revocs.d' created
gpg: revocation certificate stored as '/home/assafmo/.gnupg/openpgp-revocs.d/31EE74534094184D9964EF82B58FBB4C23247554.rev'
public and secret key created and signed.
pub rsa4096 2019-05-01 [SC]
31EE74534094184D9964EF82B58FBB4C23247554
uid My Name <[email protected]>
sub rsa4096 2019-05-01 [E]
</code></pre></div></div>
<p>You can backup your private key using:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg <span class="nt">--export-secret-keys</span> <span class="s2">"</span><span class="k">${</span><span class="nv">EMAIL</span><span class="k">}</span><span class="s2">"</span> <span class="o">></span> my-private-key.asc
</code></pre></div></div>
<p>And import it using:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg <span class="nt">--import</span> my-private-key.asc
</code></pre></div></div>
<h3 id="2-creating-the-keygpg-file">2. Creating the <code class="language-plaintext highlighter-rouge">KEY.gpg</code> file</h3>
<p>Create the ASCII public key file <code class="language-plaintext highlighter-rouge">KEY.gpg</code> inside the git repo <code class="language-plaintext highlighter-rouge">my_ppa</code>:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg <span class="nt">--armor</span> <span class="nt">--export</span> <span class="s2">"</span><span class="k">${</span><span class="nv">EMAIL</span><span class="k">}</span><span class="s2">"</span> <span class="o">></span> /path/to/my_ppa/KEY.gpg
</code></pre></div></div>
<p>Note: The private key is referenced by the email address you entered in the previous step.</p>
<h3 id="3-creating-the-packages-and-packagesgz-files">3. Creating the <code class="language-plaintext highlighter-rouge">Packages</code> and <code class="language-plaintext highlighter-rouge">Packages.gz</code> files</h3>
<p>Inside the git repo <code class="language-plaintext highlighter-rouge">my_ppa</code>:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dpkg-scanpackages <span class="nt">--multiversion</span> <span class="nb">.</span> <span class="o">></span> Packages
<span class="nb">gzip</span> <span class="nt">-k</span> <span class="nt">-f</span> Packages
</code></pre></div></div>
<h3 id="4-creating-the-release-releasegpg-and-inrelease-files">4. Creating the <code class="language-plaintext highlighter-rouge">Release</code>, <code class="language-plaintext highlighter-rouge">Release.gpg</code> and <code class="language-plaintext highlighter-rouge">InRelease</code> files</h3>
<p>Inside the git repo <code class="language-plaintext highlighter-rouge">my_ppa</code>:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt-ftparchive release <span class="nb">.</span> <span class="o">></span> Release
gpg <span class="nt">--default-key</span> <span class="s2">"</span><span class="k">${</span><span class="nv">EMAIL</span><span class="k">}</span><span class="s2">"</span> <span class="nt">-abs</span> <span class="nt">-o</span> - Release <span class="o">></span> Release.gpg
gpg <span class="nt">--default-key</span> <span class="s2">"</span><span class="k">${</span><span class="nv">EMAIL</span><span class="k">}</span><span class="s2">"</span> <span class="nt">--clearsign</span> <span class="nt">-o</span> - Release <span class="o">></span> InRelease
</code></pre></div></div>
<h3 id="5-creating-the-my_list_filelist-file">5. Creating the <code class="language-plaintext highlighter-rouge">my_list_file.list</code> file</h3>
<p>Inside the git repo <code class="language-plaintext highlighter-rouge">my_ppa</code>:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">echo</span> <span class="s2">"deb [signed-by=/etc/apt/trusted.gpg.d/my_ppa.gpg] https://</span><span class="k">${</span><span class="nv">GITHUB_USERNAME</span><span class="k">}</span><span class="s2">.github.io/my_ppa ./"</span> <span class="o">></span> my_list_file.list
</code></pre></div></div>
<p>This file will be installed later on in the user’s <code class="language-plaintext highlighter-rouge">/etc/apt/sources.list.d/</code> directory. This tells <code class="language-plaintext highlighter-rouge">apt</code> to look for updates from your PPA in <code class="language-plaintext highlighter-rouge">https://${GITHUB_USERNAME}.github.io/my_ppa</code>.</p>
<h3 id="thats-it">That’s it!</h3>
<p>Commit and push to GitHub and your PPA is ready to go:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git add <span class="nt">-A</span>
git commit <span class="nt">-m</span> <span class="s2">"my ppa repo is now hosted on github"</span>
git push <span class="nt">-u</span> origin master
</code></pre></div></div>
<p>Now you can tell all your friends and users to install your PPA this way:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-s</span> <span class="nt">--compressed</span> <span class="s2">"https://</span><span class="k">${</span><span class="nv">GITHUB_USERNAME</span><span class="k">}</span><span class="s2">.github.io/my_ppa/KEY.gpg"</span> | gpg <span class="nt">--dearmor</span> | <span class="nb">sudo tee</span> /etc/apt/trusted.gpg.d/my_ppa.gpg <span class="o">></span>/dev/null
<span class="nb">sudo </span>curl <span class="nt">-s</span> <span class="nt">--compressed</span> <span class="nt">-o</span> /etc/apt/sources.list.d/my_list_file.list <span class="s2">"https://</span><span class="k">${</span><span class="nv">GITHUB_USERNAME</span><span class="k">}</span><span class="s2">.github.io/my_ppa/my_list_file.list"</span>
<span class="nb">sudo </span>apt update
</code></pre></div></div>
<p>Then they can install your packages:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt <span class="nb">install </span>package-a package-b package-z
</code></pre></div></div>
<p>Whenever you publish a new version for an existing package your users will get it just like any other update.</p>
<h3 id="how-to-add-new-packages">How to add new packages</h3>
<p>Just put your new <code class="language-plaintext highlighter-rouge">.deb</code> files inside the git repo <code class="language-plaintext highlighter-rouge">my_ppa</code> and execute:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Packages & Packages.gz</span>
dpkg-scanpackages <span class="nt">--multiversion</span> <span class="nb">.</span> <span class="o">></span> Packages
<span class="nb">gzip</span> <span class="nt">-k</span> <span class="nt">-f</span> Packages
<span class="c"># Release, Release.gpg & InRelease</span>
apt-ftparchive release <span class="nb">.</span> <span class="o">></span> Release
gpg <span class="nt">--default-key</span> <span class="s2">"</span><span class="k">${</span><span class="nv">EMAIL</span><span class="k">}</span><span class="s2">"</span> <span class="nt">-abs</span> <span class="nt">-o</span> - Release <span class="o">></span> Release.gpg
gpg <span class="nt">--default-key</span> <span class="s2">"</span><span class="k">${</span><span class="nv">EMAIL</span><span class="k">}</span><span class="s2">"</span> <span class="nt">--clearsign</span> <span class="nt">-o</span> - Release <span class="o">></span> InRelease
<span class="c"># Commit & push</span>
git add <span class="nt">-A</span>
git commit <span class="nt">-m</span> update
git push
</code></pre></div></div>
<h3 id="sources">Sources</h3>
<ul>
<li><a href="https://makandracards.com/makandra/37763-gpg-extract-private-key-and-import-on-different-machine">Export and import a GPG key</a></li>
<li><a href="http://blog.jonliv.es/blog/2011/04/26/creating-your-own-signed-apt-repository-and-debian-packages/">Creating your own Signed APT Repository and Debian Packages
</a></li>
<li><a href="https://medium.com/sqooba/create-your-own-custom-and-authenticated-apt-repository-1e4a4cf0b864">Create your own custom and authenticated APT repository
</a></li>
<li><a href="https://github.com/tagplus5/vscode-ppa">A vscode ppa example by @tagplus5</a></li>
<li><a href="https://askubuntu.com/questions/1345/what-is-the-simplest-debian-packaging-guide">What is the simplest Debian Packaging Guide?</a></li>
</ul>Assaf MoramiPublishing your own Debian packages and hosting it on a GitHub repo is pretty easy. This is a quick HowTo.Some more progress and ETA tricks2018-06-16T00:00:00+00:002018-06-16T00:00:00+00:00https://assafmo.github.io/2018/06/16/more-progress-eta<p>In an <a href="https://assafmo.github.io/2017/08/02/pv-eta.html">earlier post</a> I wrote about <code class="language-plaintext highlighter-rouge">pv -d</code>. Here are some more tricks that can be even easier to use.</p>
<h3 id="reading-to-stdout-with-pv-instead-of-cat">Reading to stdout with pv instead of cat</h3>
<p>Reading files to stdout can be done with pv instead of cat. pv will also print transfer rate, ETA and a progress bar to stderr.</p>
<p>For example, counting packets in a pcap file:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>➜ <span class="nb">ls</span> <span class="nt">-lh</span>
total 1.0G
<span class="nt">-rw-rw-r--</span> 1 assafmo assafmo 1.0G Jun 10 20:51 my.pcap
➜ pv my.pcap | tcpdump <span class="nt">-r</span> /dev/stdin <span class="nt">-qn</span> | <span class="nb">wc</span> <span class="nt">-l</span>
reading from file /dev/stdin, link-type EN10MB <span class="o">(</span>Ethernet<span class="o">)</span>
512MiB 0:00:10 <span class="o">[</span>26.3MiB/s] <span class="o">[================></span> <span class="o">]</span> 50% ETA 0:00:09
</code></pre></div></div>
<p>Result:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>➜ pv my.pcap | tcpdump <span class="nt">-r</span> /dev/stdin <span class="nt">-qn</span> | <span class="nb">wc</span> <span class="nt">-l</span>
reading from file /dev/stdin, link-type EN10MB <span class="o">(</span>Ethernet<span class="o">)</span>
1023MiB 0:00:19 <span class="o">[</span>44.8MiB/s] <span class="o">[================================>]</span> 100%
8635943
</code></pre></div></div>
<h3 id="using-pv--s-in-between-pipes">Using <code class="language-plaintext highlighter-rouge">pv -s</code> in between pipes</h3>
<p>Placing pv in between pipes will make it read from stdin and print to stdout (just like cat), and also print the transfer rate to stderr.</p>
<p>By using the <code class="language-plaintext highlighter-rouge">-s</code> flag we can tell pv the total size of the data being transferred, thus pv will also print progress and ETA to stderr.</p>
<p>For example, merging multiple pcap files together:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>➜ <span class="nb">ls</span> <span class="nt">-lh</span>
total 3.0G
<span class="nt">-rw-rw-r--</span> 1 assafmo assafmo 1.0G Jun 10 20:51 1.pcap
<span class="nt">-rw-rw-r--</span> 1 assafmo assafmo 1.0G Jun 10 20:51 2.pcap
<span class="nt">-rw-rw-r--</span> 1 assafmo assafmo 1.0G Jun 10 20:51 my.pcap
➜ joincap <span class="k">*</span>.pcap | pv <span class="nt">-s</span> <span class="si">$(</span><span class="nb">du</span> <span class="nt">-bc</span> <span class="k">*</span>pcap | <span class="nb">tail</span> <span class="nt">-1</span> | <span class="nb">cut</span> <span class="nt">-f</span> 1<span class="si">)</span> <span class="o">></span> merged.pcap
1.16GiB 0:00:04 <span class="o">[</span> 275MiB/s] <span class="o">[============></span> <span class="o">]</span> 38% ETA 0:00:06
</code></pre></div></div>
<p><em>Note: <code class="language-plaintext highlighter-rouge">du -bc *pcap | tail -1 | cut -f 1</code> gives us an approximation for the size of <code class="language-plaintext highlighter-rouge">merged.pcap</code>. This is good enough, as <code class="language-plaintext highlighter-rouge">pv -s</code> won’t crash if the data is bigger or smaller, it’ll just be wrong (exit before or go over 100%).</em></p>
<h3 id="using-parallel---bar-to-count-done-jobs">Using <code class="language-plaintext highlighter-rouge">parallel --bar</code> to count done jobs</h3>
<p>Finally, GNU parallel can print how many jobs are done and an ETA with the <code class="language-plaintext highlighter-rouge">--bar</code> flag.</p>
<p>For example, counting packets in multiple pcaps:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>➜ <span class="nb">ls</span> <span class="k">*</span>pcap | parallel <span class="nt">--bar</span> <span class="s1">'tcpdump -r "{}" -qn | wc -l > "{}".count'</span>
66% 2:1<span class="o">=</span>1s my.pcap
</code></pre></div></div>Assaf MoramiIn an earlier post I wrote about pv -d. Here are some more tricks that can be even easier to use.Mounting partitions from a dd image2018-05-09T00:00:00+00:002018-05-09T00:00:00+00:00https://assafmo.github.io/2018/05/09/mount-dd<p><em>UPDATE: Some users on HN <a href="https://news.ycombinator.com/item?id=17028512">[1]</a> and reddit <a href="https://www.reddit.com/r/commandline/comments/8i4o3n/mounting_partitions_from_a_dd_image/">[2]</a><a href="https://www.reddit.com/r/linux/comments/8i4nzv/mounting_partitions_from_a_dd_image/">[3]</a><a href="https://www.reddit.com/r/programming/comments/8i4nxy/mounting_partitions_from_a_dd_image/">[4]</a> pointed out that the <code class="language-plaintext highlighter-rouge">fdisk</code> method is outdated. The same result can be achieved without using offsets with:</em></p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">DEV</span><span class="o">=</span><span class="si">$(</span><span class="nb">sudo </span>losetup <span class="nt">--partscan</span> <span class="nt">--find</span> <span class="nt">--read-only</span> <span class="nt">--show</span> x.dd<span class="si">)</span>
<span class="nb">mkdir </span>partition_1 partition_2
<span class="nb">sudo </span>mount <span class="nt">-t</span> ntfs <span class="nt">-o</span> ro <span class="s2">"</span><span class="k">${</span><span class="nv">DEV</span><span class="k">}</span><span class="s2">p1"</span> partition_1
<span class="nb">sudo </span>mount <span class="nt">-t</span> ntfs <span class="nt">-o</span> ro <span class="s2">"</span><span class="k">${</span><span class="nv">DEV</span><span class="k">}</span><span class="s2">p2"</span> partition_2
</code></pre></div></div>
<h3 id="the-old-way---using-fdisk">The old way - using fdisk</h3>
<p>When <code class="language-plaintext highlighter-rouge">x.dd</code> is a disk clone created with one of the <code class="language-plaintext highlighter-rouge">dd</code> commands, first we’ll try to detect partitions:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>fdisk <span class="nt">-l</span> x.dd
Disk x.dd: 94.1 MiB, 98705408 bytes, 192784 sectors
Units: sectors of 1 <span class="k">*</span> 512 <span class="o">=</span> 512 bytes
Sector size <span class="o">(</span>logical/physical<span class="o">)</span>: 512 bytes / 512 bytes
I/O size <span class="o">(</span>minimum/optimal<span class="o">)</span>: 512 bytes / 512 bytes
Disklabel <span class="nb">type</span>: dos
Disk identifier: 0xfc23b344
Device Boot Start End Sectors Size Id Type
x.dd1 63 96389 96327 47M 7 HPFS/NTFS/exFAT
x.dd2 96390 192779 96390 47.1M 7 HPFS/NTFS/exFAT
</code></pre></div></div>
<p>This means that our <code class="language-plaintext highlighter-rouge">x.dd</code> image has two partitions, both NTFS. The offset of the first partition is 63 units, and the offset of the second partitions is 96,390 units. Each unit is of size 512 bytes.</p>
<p>In order to mount those partitions, we’ll use the <code class="language-plaintext highlighter-rouge">mount</code> command with the <code class="language-plaintext highlighter-rouge">offset</code> option:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir </span>partition_1 partition_2
mount <span class="nt">-t</span> ntfs <span class="nt">-o</span> ro,offset<span class="o">=</span><span class="k">$((</span><span class="m">512</span><span class="o">*</span><span class="m">63</span><span class="k">))</span> x.dd partition_1
mount <span class="nt">-t</span> ntfs <span class="nt">-o</span> ro,offset<span class="o">=</span><span class="k">$((</span><span class="m">512</span><span class="o">*</span><span class="m">96390</span><span class="k">))</span> x.dd partition_2
</code></pre></div></div>Assaf MoramiUPDATE: Some users on HN [1] and reddit [2][3][4] pointed out that the fdisk method is outdated. The same result can be achieved without using offsets with:Working offline with Yarn2018-04-11T00:00:00+00:002018-04-11T00:00:00+00:00https://assafmo.github.io/2018/04/11/yarn-offline<p>NPM is not very friendly when working with Node.js in an offline environment. Multiple times I found myself <code class="language-plaintext highlighter-rouge">npm install</code>ing on an internet machine just to copy <code class="language-plaintext highlighter-rouge">node_modules</code> to the offline environment and commit it with the entire project.</p>
<p>For a while <a href="https://github.com/arei/npmbox">npmbox</a> worked for me, but issues like <a href="https://github.com/arei/npmbox/issues/61">trying to reach the internet</a> would randomly appear and cripple my workflow.</p>
<p>I also tried <a href="https://github.com/rlidwka/sinopia">Sinopia</a>, but couldn’t consistently publish new or updated packages to it.</p>
<h3 id="enter-yarn">Enter Yarn</h3>
<p>With Yarn I’m able to consistently install packages in an offline environment. Their <a href="https://yarnpkg.com/blog/2016/11/24/offline-mirror/">original blog post</a> is helpful, but I met some edge case it doesn’t cover.</p>
<p>This is my process for using Yarn in an offline environment.</p>
<h3 id="configuring-yarn-offline-mirror">Configuring <code class="language-plaintext highlighter-rouge">yarn-offline-mirror</code></h3>
<h4 id="on-the-internet-machine">On the internet machine:</h4>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yarn config <span class="nb">set </span>yarn-offline-mirror ~/yarn-offline-mirror/
</code></pre></div></div>
<h4 id="on-the-offline-machine">On the offline machine:</h4>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yarn config <span class="nb">set </span>yarn-offline-mirror ~/yarn-offline-mirror/
</code></pre></div></div>
<p>Note: On the offline machine <code class="language-plaintext highlighter-rouge">~/yarn-offline-mirror/</code> can also be a shared folder or a git repository.</p>
<h3 id="creating-a-new-project">Creating a new project</h3>
<h4 id="on-the-internet-machine-1">On the internet machine:</h4>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir </span>new-project/
<span class="nb">cd </span>new-project/
yarn add [email protected] <span class="o">[</span>dep2...]
</code></pre></div></div>
<p>Then copy <code class="language-plaintext highlighter-rouge">new-project/yarn.lock</code>, <code class="language-plaintext highlighter-rouge">new-project/package.json</code> and <code class="language-plaintext highlighter-rouge">~/yarn-offline-mirror/</code> to the offline machine.</p>
<p>(<code class="language-plaintext highlighter-rouge">rm -rf new-project/</code> is ok now.)</p>
<h4 id="on-the-offline-machine-1">On the offline machine:</h4>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir </span>new-project/
<span class="nb">cp</span> /path/to/imported/<span class="o">{</span>yarn.lock,package.json<span class="o">}</span> new-project/
<span class="nb">cp</span> <span class="nt">-n</span> /path/to/imported/yarn-offline-mirror/<span class="k">*</span> ~/yarn-offline-mirror/
<span class="nb">cd </span>new-project/
yarn <span class="nt">--offline</span>
</code></pre></div></div>
<h3 id="adding-packages-to-an-existing-project">Adding packages to an existing project</h3>
<h4 id="on-the-internet-machine-2">On the internet machine:</h4>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir </span>new-packages/
<span class="nb">cd </span>new-packages/
yarn add [email protected] <span class="o">[</span>dep2...]
</code></pre></div></div>
<p>Then copy <code class="language-plaintext highlighter-rouge">new-packages/yarn.lock</code>, <code class="language-plaintext highlighter-rouge">new-packages/package.json</code> and <code class="language-plaintext highlighter-rouge">~/yarn-offline-mirror/</code> to the offline machine.</p>
<h4 id="on-the-offline-machine-2">On the offline machine:</h4>
<ol>
<li>
<p>Append the imported <code class="language-plaintext highlighter-rouge">yarn.lock</code> to the existing <code class="language-plaintext highlighter-rouge">yarn.lock</code>. I found that without this step Yarn sometimes fails to find the new packages in the offline cache:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cat</span> /path/to/imported/yarn.lock <span class="o">>></span> existing-project/yarn.lock
</code></pre></div> </div>
</li>
<li>
<p>Update <code class="language-plaintext highlighter-rouge">package.json</code> with the new dependencies. This means merging both <code class="language-plaintext highlighter-rouge">dependencies</code> fields together. An ugly one-liner I tend to use:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cat </span>existing-project/package.json <<span class="o">(</span><span class="nb">cat </span>existing-project/package.json /path/to/imported/package.json |
jq <span class="s1">'.dependencies'</span> |
jq <span class="nt">-s</span> <span class="s1">'add | {dependencies: .}'</span><span class="o">)</span> |
jq <span class="nt">-s</span> add |
sponge existing-project/package.json
</code></pre></div> </div>
</li>
<li>
<p>Update <code class="language-plaintext highlighter-rouge">yarn-offline-mirror</code>:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cp</span> <span class="nt">-n</span> /path/to/imported/yarn-offline-mirror/<span class="k">*</span> ~/yarn-offline-mirror/
</code></pre></div> </div>
</li>
<li>
<p>Install the new packages. This step also fixes <code class="language-plaintext highlighter-rouge">existing-project/yarn.lock</code> after we messed with it in step 1.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd </span>existing-project/
yarn <span class="nt">--offline</span>
</code></pre></div> </div>
</li>
</ol>
<p>I found the if I skip steps 1 and 2, and in step 4 I do <code class="language-plaintext highlighter-rouge">yarn add --offline <dep1> [<dep2>...]</code> then Yarn might not find the new packages in the cache and fail. This bug still exists in version 1.5.1. I believe it is related to these GitHub issues: <a href="https://github.com/yarnpkg/yarn/issues/5454">[1]</a><a href="https://github.com/yarnpkg/yarn/issues/5339">[2]</a><a href="https://github.com/yarnpkg/yarn/issues/731">[3]</a><a href="https://github.com/yarnpkg/yarn/issues/4909">[4]</a><a href="https://github.com/yarnpkg/yarn/issues/4266">[5]</a><a href="https://github.com/yarnpkg/yarn/issues/4899">[6]</a>.</p>
<h3 id="installing-packages-globally">Installing packages globally</h3>
<p>Yarn <a href="https://stackoverflow.com/a/43901681">discourages using global packages</a>, so it’s hard by design to install them.</p>
<ol>
<li>
<p>Find out where is the global installation location <a href="https://yarnpkg.com/lang/en/docs/cli/global/#defining-install-location">[7]</a>:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yarn global bin
</code></pre></div> </div>
<p>(Or set it with <code class="language-plaintext highlighter-rouge">yarn config set prefix <file_path></code>)</p>
</li>
<li>
<p>Add it to your path. E.g.:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">echo</span> <span class="s1">'export PATH=$PATH:'</span><span class="s2">"</span><span class="si">$(</span>yarn global bin<span class="si">)</span><span class="s2">"</span> <span class="o">>></span> ~/.bashrc
<span class="nb">source</span> ~/.bashrc <span class="c"># reload</span>
</code></pre></div> </div>
</li>
<li>
<p>Similar to <a href="#creating-a-new-project">Creating a new project</a>, but with a few subtle differences:</p>
<h4 id="on-the-internet-machine-3">On the internet machine:</h4>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir </span>new-cli/
<span class="nb">cd </span>new-cli/
yarn add [email protected] <span class="o">[</span>cli2...]
</code></pre></div> </div>
<p>Then copy <code class="language-plaintext highlighter-rouge">new-cli/yarn.lock</code> and <code class="language-plaintext highlighter-rouge">~/yarn-offline-mirror/</code> to the offline machine.</p>
<p>(<code class="language-plaintext highlighter-rouge">rm -rf new-cli/</code> is ok now.)</p>
<h4 id="on-the-offline-machine-3">On the offline machine:</h4>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cp</span> /path/to/imported/yarn.lock <span class="nb">.</span>
<span class="nb">cp</span> <span class="nt">-n</span> /path/to/imported/yarn-offline-mirror/<span class="k">*</span> ~/yarn-offline-mirror/
yarn global add <span class="nt">--offline</span> [email protected] <span class="o">[</span>cli2...]
<span class="nb">rm</span> <span class="nt">-f</span> ./yarn.lock
</code></pre></div> </div>
<p>Note: In this context we don’t care about <code class="language-plaintext highlighter-rouge">package.json</code>. We only need to make sure that Yarn can find <code class="language-plaintext highlighter-rouge">yarn.lock</code> in the current directory and that <code class="language-plaintext highlighter-rouge">~/yarn-offline-mirror/</code> has the needed dependencies.</p>
</li>
</ol>
<h3 id="this-is-not-a-silver-bullet">This is not a silver bullet</h3>
<p>There are probably more edge cases I still haven’t met. I wish the docs around <code class="language-plaintext highlighter-rouge">yarn.lock</code> were better, Because playing with this file solved most of my problems.</p>
<p>For example when adding packages to an existing project, I still don’t understand why appending the new lock file to the existing one solves the resolution problem. According to the docs this step is unnecessary. And sometimes it is, but some other times it isn’t.</p>
<p>Overall I’m very satisfied with Yarn and I have a feeling it is only going to get better.</p>Assaf MoramiNPM is not very friendly when working with Node.js in an offline environment. Multiple times I found myself npm installing on an internet machine just to copy node_modules to the offline environment and commit it with the entire project.Better progress and ETA using the pv command2017-08-02T00:00:00+00:002017-08-02T00:00:00+00:00https://assafmo.github.io/2017/08/02/pv-eta<p>Sometimes we need to run a task that takes a lot of time to finish. Since version 1.5.1, the pv command <a href="http://www.ivarch.com/programs/pv.shtml">[1]</a><a href="http://manpages.ubuntu.com/manpages/xenial/man1/pv.1.html">[2]</a> has this neat option <code class="language-plaintext highlighter-rouge">pv -d $PID</code> that will monitor open file descriptors of the process <code class="language-plaintext highlighter-rouge">$PID</code>.</p>
<p>The pv command can detect when a process is reading a file from beginning to end, and it will kindly print transfer rate, ETA and a progress bar.</p>
<p>For example, We have a large CSV file that we want to load into a SQLite table.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>➜ ~ ls -lh my.csv
-rw-r--r-- 1 assafmo assafmo 16G Aug 2 08:41 my.csv
➜ ~ sqlite3 my.db
SQLite version 3.16.2 2017-01-06 16:32:41
Enter ".help" for usage hints.
sqlite> .mode csv
sqlite> .import ./my.csv my_table
</code></pre></div></div>
<p>This process can take a lot of time and we get no indication from sqlite3 when it will finish.</p>
<p>We need to find out what process preforms the reading and get its PID. This can be cat, sed, awk and many others, but in our case it’s sqlite3.</p>
<p>Using <code class="language-plaintext highlighter-rouge">pv -d $(pidof sqlite3)</code> gives us a nice progress bar and ETA for sqlite3 reading our file.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>➜ ~ pv -d $(pidof sqlite3)
3:/home/assafmo/my.db: 0 B 0:00:07 [ 0 B/s] [<=> ]
4:/home/assafmo/my.csv: 152MiB 0:00:07 [1.96MiB/s] [> ] 0% ETA 2:14:37
5:/home/assafmo/my.db-journal: 0 B 0:00:07 [ 0 B/s] [<=> ]
</code></pre></div></div>
<p>Similarly, we can get progress and ETA when copying files with <code class="language-plaintext highlighter-rouge">rsync -P -h</code> instead of <code class="language-plaintext highlighter-rouge">cp</code>.</p>Assaf MoramiSometimes we need to run a task that takes a lot of time to finish. Since version 1.5.1, the pv command [1][2] has this neat option pv -d $PID that will monitor open file descriptors of the process $PID.