<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[zzpxyx]]></title><description><![CDATA[zzpxyx]]></description><link>www.zzpxyx.com</link><generator>metalsmith-feed</generator><lastBuildDate>Sat, 11 Apr 2026 02:59:55 GMT</lastBuildDate><atom:link href="rss.xml" rel="self" type="application/rss+xml"/><item><title><![CDATA[Repurposing a 2014 MacBook Air]]></title><description><![CDATA[<p>Recently, I’ve managed to repurpose an old 2014 MacBook Air into a Linux home server.</p>
<p>I have a 2014 MacBook Air whose battery performance has deteriorated and the last available macOS version feels very sluggish on it. Its official trade-in value has gone down to zero. However, it is still a functioning computer, so I want to repurpose it as a home server to beef up <a href="/posts/repurposing-an-old-android-phone/">my home computing power</a>.</p>
<p><strong>An obliged warning first.</strong> It is very dangerous to keep a lithium-ion battery plugged in all the time especially if the battery is old. The battery could swell or even explode. I could afford this because I’m watching the MacBook Air closely with periodic battery checks.</p>
<p>At first I tried to use the MacBook Air as is for a home server. The macOS 11 Big Sur is slow for daily GUI-based tasks, but it is not that bad if I just SSH into it. The major blocks are the security concerns and the limited software repos. I’m afraid of security issues in old macOS versions even though I only plan to use the server internally. Homebrew no longer has pre-built packages for this macOS version. Also, I’m interested in trying Linux on a MacBook, so I wiped the macOS and started from scratch.</p>
<p>I joined the bandwagon and chose Ubuntu Server 24.04.4 LTS as the distro. The installation process was fine overall with a few bumpy spots. First, I used KDE ISO Image Writer to create a bootable installation media. I ran into a “last block not fully written” error. <a href="https://www.reddit.com/r/kde/comments/1gh2hst/comment/luv6na7">This post</a> provided the correct solution. In short, I should not mount the USB device. Second, the 2014 MacBook Air needed a proprietary Broadcom WiFi driver. I chose “set up network later” in the installation wizard but it was so hard getting a WiFi connection later on. I gave up eventually after intense trial and error, and restarted the installation wizard with a USB WiFi dongle. Third, it took me a while to understand what was going on in the disk partition step.</p>
<p>The machine was usable immediately after the installation finished. Ubuntu Server has some sane defaults like disabling the root account by default. This saved quite a bit of initial system administration work. I did some enhancements nonetheless as documented below.</p>
<p><strong>Limit charging threshold for the battery.</strong> I installed the <code>tlp</code> package and used <a href="https://github.com/c---/applesmc-next">this patch</a> to limit the charging threshold to 50%. Better to be cautious about batteries.</p>
<p><strong>Switch to the internal network adapter.</strong> I installed the <code>bcmwl-kernel-source</code> package for the internal WiFi adapter. Then I created a new WiFi config file with the same structure as the old one <code>/etc/netplan/50-cloud-init.yaml</code> which contained the configuration for the USB WiFi dongle used during installation. After that, I removed the old config file.</p>
<p><strong>Keep it awake.</strong> I made the following drop-in config for logind. Now, closing the lid won’t trigger hibernation.</p>
<pre><code class="hljs language-ini"><span class="hljs-section">[Login]</span>
<span class="hljs-attr">HandleLidSwitch</span>=ignore
<span class="hljs-attr">HandleLidSwitchExternalPower</span>=ignore
</code></pre>
<p><strong>Shut down the screen.</strong> I added the following kernel parameter in <code>/etc/default/grub</code> to shut down the screen after 30 seconds of inactivity.</p>
<pre><code class="hljs language-ini"><span class="hljs-attr">GRUB_CMDLINE_LINUX_DEFAULT</span>=<span class="hljs-string">&quot;consoleblank=30&quot;</span>
</code></pre>
<p><strong>Use certificates for SSH.</strong> I chose password authentication during the installation process for simplicity, but I wanted to change that post install. To do this, I removed the default <code>/etc/ssh/sshd_config.d/50-cloud-init.conf</code> and used the following drop-in config instead.</p>
<pre><code class="hljs language-text">PasswordAuthentication no
</code></pre>
<p>Now, the MacBook Air is functioning as a Linux home server. I tried Jellyfin and it worked very well. I migrated my <a href="/posts/sharing-security-camera-view/">security camera setup</a>. It is a long story and I’ll share more some other day.</p>
<p>I even used a watt meter to gauge its power consumption. The average power is about 8W. The Android phone server is about 1W, give or take. The MacBook Air server is definitely not ideal compared to modern hardware, but it is indeed cost-effective compared to buying new.</p>
]]></description><link>posts/repurposing-a-2014-macbook-air</link><guid isPermaLink="true">posts/repurposing-a-2014-macbook-air</guid></item><item><title><![CDATA[In Praise of Linux Desktop]]></title><description><![CDATA[<p>I’ve been running a Linux desktop environment exclusively for half a year. Despite the minor issues along the way, I’m amazed by how much Linux desktop has improved over the years.</p>
<p>I have been using Linux desktop for a long time, but it has always been a dual boot with a Windows install. I use Windows mostly for games and some specific use cases. I tried Wine back then but it wasn’t ideal. This setup has been working well until the end of life for Windows 10. I’m among millions of others where the hardware is still fine but Windows 11 just needs more. All things considered, I decide to go all in for Linux desktop.</p>
<p>Considering that running games is still a priority for me, I want to stay close to Steam Deck’s setup so that maybe it’s easier to find solutions in case I run into any issue. Steam Deck uses Arch + KDE, and I’ll follow that.</p>
<p>I’m pleasantly surprised to see that things have improve a lot since last time I tried installing Arch Linux from scratch. For example, the <code>tmp</code> folder is mounted in RAM automatically. No more manual editing for <code>fstab</code>. It’s also a smooth experience installing KDE Plasma 6.</p>
<p>I still remember the old days of fighting KDE Plasma 4 issues on an Ubuntu install nearly two decades ago, so I’m shocked when I see KDE Plasma 6. It’s so polished and usable and definitely no longer the KDE I have in memory. The out-of-box experience is superb. I don’t need to configure any hardware driver or anything. The default settings just work.</p>
<p>My excitement peaks again when I run Steam games. They just work no matter whether they are triple-A or indie games. Thanks to Valve’s Wine-based compatibility layer, Linux gaming is finally enjoyable.</p>
<p>I feel obliged to document the drawbacks as well. First, KDE Plasma 6 still has some minor issues. For example, my speaker volume keeps resetting to zero and back. This causes the on-screen display for volume to appear frequently. I think the root cause might actually be my hardware, especially the front panel headphone port. As a workaround, I have to use <code>hdajackretask</code> to override the rear line-out port with the front headphone port. Second, some websites just won’t work properly on Linux-based web browsers, especially the login pages. I have to use another device for those websites. Third, my specific use cases that require Windows or macOS have reduced a lot over the years, but they are not down to zero yet. For example, I still don’t know how to copy the m4a files out if I purchase some music from iTunes on an iPhone. Luckily, I have a MacBook nearby.</p>
<p>Linux desktop has been the new norm for me for the past half year. I keep questioning myself why I haven’t done it sooner. Also, I’m deeply grateful for all the work that folks have put into this.</p>
]]></description><link>posts/in-praise-of-linux-desktop</link><guid isPermaLink="true">posts/in-praise-of-linux-desktop</guid></item><item><title><![CDATA[Use Correct USB Port]]></title><description><![CDATA[<p>This is a memo to myself that I should use the correct USB port for a USB 3.0 flash drive.</p>
<p>The other day I plugged a USB 3.0 flash drive into the USB port on the front panel of my desktop. It didn’t mount. Instead, it gave an error that looked like <code>device descriptor read/64, error -110</code>. I didn’t know how I acquired this knowledge but my first thoughts were about insufficient USB power supply. I switched the flash drive to one of the back USB ports, which are directly from the motherboard. It didn’t solve the issue. Weird.</p>
<p>I did some searching on the internet and the most relevant content was <a href="https://forum.zorin.com/t/usb-descriptor-read-64-error-32/4718/2">this post</a>, which also suggested power issues. I tried a different back USB port, and it worked this time. I suddenly remembered that on the back there are a mixture of USB 2.0 and USB 3.0 ports, and I should probably use the USB 3.0 port.</p>
<p>The issue is solved for now, but I don’t know the root cause. This exact flash drive used to work on the front panel USB port in Linux, and in Windows it has always worked without issues no matter where it is plugged in. I’m not sure what has changed.</p>
]]></description><link>posts/use-correct-usb-port</link><guid isPermaLink="true">posts/use-correct-usb-port</guid></item><item><title><![CDATA[Rclone in Termux on Android 6]]></title><description><![CDATA[<p>Recently, I’ve had a couple of lessons learned when running Rclone in Termux on Android 6.</p>
<p>A bit of background story. I’ve been using an old Android phone as a tiny home server as mentioned in <a href="/posts/repurposing-an-old-android-phone/">my previous post</a>. It has been running an old version of Rclone in Termux to <a href="/posts/sharing-security-camera-view/">share a security camera view</a>. Recently, something has changed on my cloud provider and Rclone gives an error about authentication when uploading files. I went on a journey of fixing the issues.</p>
<p>I need to test the most recent version of Rclone (version 1.69.2 at the time of investigation) as the issue is reported to have already been fixed. I was looking for an Android build of Rclone, but I couldn’t find any that worked on Android 6. It took me quite a while before I realized that Termux not only is a terminal emulator but also installs a base Linux environment. In other words, I actually have an ARMv7 Linux-like system on the Nexus 5. I could just install the <a href="https://rclone.org/downloads/">ARMv7 build of Rclone</a> and it may work. In fact, it worked perfectly (well, read on).</p>
<p>The next issue is about DNS resolver. Rclone thinks that it is running on an Linux system so it sends DNS requests to port 53. In Termux for Android 6, the commonly used DNS resolver like dnsmasq is not in the Termux package repo. I wanted to edit <code>/etc/resolv.conf</code> but it wasn’t straightforward on an Android phone. After researching for quite a while, I found that Termux has its own <code>resolv.conf</code>, just not under the <code>/etc</code> folder. Then I learned that Termux <a href="https://wiki.termux.com/wiki/Differences_from_Linux">has differences from Linux</a>. In my case, it’s missing the Filesystem Hierarchy Standard (all files and directories are under the root directory). Apparently, there is this magic command <code>termux-chroot</code> that can mimic the classic Linux file system layout as mentioned in that Termux wiki page.</p>
<p>The final issue is about SSL certificates. Up to this point, I could run Rclone with the option <code>--no-check-certificate</code> to bypass some x509 certificate error, but it was not safe to do so. I tried to upgrade the <code>ca-certificate</code> package in Termux and even manually installed certificates in the Android system, but it didn’t help. After another long period of researching, I found that Termux has certificates installed in a different location. I just needed to point Rclone to them using <code>--ca-cert=/etc/tls/cert.pem</code>. It worked immediately.</p>
<p>It was quite a long run figuring out all the issues, but it was good learning experience. We’ll see how far this tiny home server can go.</p>
]]></description><link>posts/rclone-in-termux-on-android-6</link><guid isPermaLink="true">posts/rclone-in-termux-on-android-6</guid></item><item><title><![CDATA[Boot to Text to Rescue Display Driver]]></title><description><![CDATA[<p>I troubleshooted a display driver issue the other day. Here are some notes in case I see it again in the future.</p>
<p>The other day, the monitor turned off after I booted the machine to Arch Linux. My immediate thought was that the graphics card died as that happened to me before. However, I tried typing in my password to log in, and the hard disk light did flash happily, indicating that the system was working. It was just the monitor that somehow went offline. OK, time to unplug the monitor and plug it again. Didn’t help. What about rebooting? Same blank screen. Was it a hardware issue or not? I rebooted into Windows and it worked, so definitely not hardware issues. At that time, I was pretty sure that the cause was a system upgrade on kernel and the display driver, but the question was how to fix them. I even tried blind typing commands without the monitor, but it was a bit too hard.</p>
<p>Now it was time to do some serious rescuing. I thought I knew how to boot into the text mode, but apparently I had long forgotten it. After some internet searching, I found a way to do that. It was as simple as adding a <code>3</code> for the boot parameter in GRUB, at the line that had <code>vmlinuz</code>, after other parameters like <code>rw quiet</code>. It essentially meant to boot into the runlevel 3, which had networking but not the graphical user interface.</p>
<p>With a fully working text console, I could <a href="/posts/downgrading-kernel/">downgrade packages</a>. I first downgraded the Linux kernel. After rebooting, I could see the desktop, but the wallpaper didn’t show up. Weird. Then I downgraded the nVidia display driver (both <code>nvidia</code> and <code>nvidia-utils</code> packages), and this time everything went back to normal.</p>
<p>I also found that I was not the only one affected. Apparently, there were some issues about the 555 version of the display driver.</p>
<p>Later, I realized that I could just SSH into the machine after blind logging in. It would have made this a lot simpler. This is the other reason why I need to write this post.</p>
]]></description><link>posts/boot-to-text-to-rescue-display-driver</link><guid isPermaLink="true">posts/boot-to-text-to-rescue-display-driver</guid></item><item><title><![CDATA[Typeset Chinese in Arch TeX Live]]></title><description><![CDATA[<p>I’ve changed my TeX Live setup again to accommodate typesetting Chinese.</p>
<p>Recently, I wanted to typeset a Chinese document. It has been years since the last time I did that. Not surprisingly, pdfLaTex still doesn’t support CJK characters out of the box. I remembered using XeLaTeX with the CTeX package back then, so I tried to do the same this time. As mentioned in my <a href="/posts/switching-to-arch-linux-tex-live/">previous post</a>, I use the modified <code>tlmgr</code> to manage one-off TeX packages. However, using it to install the CTeX package gave me some critical errors about not being able to install binary files in <code>tlmgr</code> user mode. Apparently, it’s time to put an end to this.</p>
<p>I’d like to clarify my goals here. First of all, I’d like to be able to typeset Chinese. In addition, I still want to compile my resume, which uses some extra fonts. After some trial and error, here is how I achieved both goals.</p>
<p>I started with the minimum number of Arch TeX Live packages. If I’m missing one of those, I will see errors like font not loadable or missing style files. This time I’ve decided to bite the bullet and install the huge extra font package since I’m all in Arch TeX Live packages. Here is the list that worked for me:</p>
<ul>
<li><code>textlive-langchinese</code>, which pulls in some additional Arch TeX Live packages like <code>texlive-bin</code> and <code>texlive-basic</code>.</li>
<li><code>texlive-latex</code>, for solving the missing <code>expl3</code> issue.</li>
<li><code>texlive-latexrecommended</code>, for getting the <code>fontspec</code> package.</li>
<li><code>texlive-latexextra</code>, for getting the <code>titlesec</code> package.</li>
<li><code>texlive-fontsrecommended</code>, for getting basic fonts like Latin Modern.</li>
<li><code>texlive-fontsextra</code>, for getting the extra fonts like Raleway.</li>
</ul>
<p>As part of the transition, I no longer need to use the modified <code>tlmgr</code> so I removed the alias that specified the user mode for it.</p>
<p>Now, on one hand, I can use XeLaTeX plus the CTeX package to typeset a Chinese document. On the other hand, I can use pdfLaTeX to compile my resume. It’s beyond my knowledge why I can’t use XeLaTeX to build my resume. I guess it’s because different TeX engines require different setups for fonts, but what do I know?</p>
<p><strong>Updates on 2024-05-07:</strong> Now, I recall something. The extra fonts like Raleway in the Arch TeX Live package didn’t work out of the box in my case. It seemed to be caused by something in the user texmf tree from my previous install. I deleted <code>~/texmf</code> completely, and the issue was resolved. Maybe I also ran <code>sudo updmap-sys --syncwithtrees</code>? Just in case it helps.</p>
]]></description><link>posts/typeset-chinese-in-arch-tex-live</link><guid isPermaLink="true">posts/typeset-chinese-in-arch-tex-live</guid></item><item><title><![CDATA[Sharing Security Camera View]]></title><description><![CDATA[<p>I’m on my journey of finding the best way of sharing a security camera view. I have an acceptable solution at the moment.</p>
<h3>Problem</h3>
<p>The core problem I’m faced with is very simple. I want to view my security camera from anywhere, and I would like to share the view with a small group of people. There are some restrictions though. First, privacy is of the utmost importance. Second, anywhere really means around the globe. The implication is that VPNs may not work well and certain mobile apps may not be available. Third, ease of use and maintainability is a key factor, so vendor lock-in should be avoided.</p>
<p>After some trail and error, I couldn’t find a way to share the live view of the security camera. Instead, I could share recordings that approximate a live view in a real time fashion. Here is what I did.</p>
<h3>Step 1: Set up Security Camera</h3>
<p>Find a good security camera and set it up properly. One key feature is supporting the ONVIF specification, or at least the camera is able to stream via the RTSP protocol. Nowadays, security cameras often need the internet access and require mobile apps for the initial setup, so another key feature for the security camera is to be able to work without the internet access after the initial setup. I’d like to isolate the security camera to minimize the risk. My router doesn’t have the virtual LAN functionality, so the best I could do was to put the security camera by itself on the 2.4 GHz band WiFi, used a different password between the 2.4 GHz and the 5 GHz bands, and set up firewall in the router to ban all incoming and outgoing traffic for the security camera.</p>
<h3>Step 2: Enable RTSP Streaming</h3>
<p>Enable the RTSP streaming on the security camera. Usually, this is done in the companion mobile app for the security camera, and it requires setting up a username and a password. The URL to the RTSP stream also varies according to security camera models. In the unfortunate case that it is not documented, the URL can usually be found through the ONVIF specification. Here is one <a href="https://superuser.com/a/1711576">example</a>. Granted, the mobile app for the security camera already shows the live view. The setup here is mostly for the next step, but it also enables watching the live view on third-party open source applications. For example, on VLC, I can use the “Open Network Stream” option, and use the URL <code>rtsp://&lt;user&gt;:&lt;pw&gt;@&lt;camera_ip&gt;:&lt;port&gt;/path/to/stream</code>. I can even use <a href="https://ffmpeg.org/ffplay.html">FFplay</a> in the same sense as shown below. More discussions on the command line parameters later.</p>
<pre><code class="hljs language-bash">ffplay -i <span class="hljs-string">&#x27;rtsp://&lt;user&gt;:&lt;pw&gt;@&lt;camera_ip&gt;:&lt;port&gt;/path/to/stream&#x27;</span>
</code></pre>
<h3>Step 3: Set up Computing Device</h3>
<p>Find a computing device. Mine is the old Android phone in <a href="/posts/repurposing-an-old-android-phone/">my previous post</a>. A Raspberry Pi or the like is perfectly fine. A home lab or a home server also works. The key point here is to find a device that can run all day every day, and supports <a href="https://ffmpeg.org/">FFmpeg</a>, <a href="https://rclone.org/">Rclone</a>, and <a href="https://github.com/inotify-tools/inotify-tools">inotify-tools</a>. Technically, <code>inotify-tools</code> can be replaced by other similar tools. See Step 5 for further details.</p>
<h3>Step 4: Set up Cloud Storage</h3>
<p>Finding the right cloud storage provider is a non-trivial task. On one hand, all security camera recordings will go there; on the other hand, it should be available to the small group of people so that I can share the uploaded recording files to them. Self-hosting may be a viable solution, but for now I’ll just use a common one. I set up the access to the cloud storage via Rclone’s <code>rclone config</code> command, which started a nice interactive configuration session.</p>
<h3>Step 5: Monitor File System Events</h3>
<p>Monitor file system events to upload recording files when they are ready. I’m pleasantly surprised to see <code>inotify-tools</code> in Termux packages, even for Android 6. Here is the command I use to monitor the folder <code>&lt;watch_folder&gt;</code> which will contain the recording files.</p>
<pre><code class="hljs language-bash">inotifywait --monitor --event close_write &lt;watch_folder&gt; |
  <span class="hljs-keyword">while</span> <span class="hljs-built_in">read</span> path event file; <span class="hljs-keyword">do</span>
    rclone moveto --no-traverse $path<span class="hljs-variable">$file</span> &lt;remote&gt;:&lt;folder&gt;/<span class="hljs-variable">${file:0:8}</span>/<span class="hljs-variable">${file:9:2}</span>/<span class="hljs-variable">$file</span>;
  <span class="hljs-keyword">done</span> &amp;
</code></pre>
<p>It basically says that whenever a recording file is finished writing, use Rclone to move the file from local to the remote cloud storage. The <code>--no-traverse</code> option is just a small optimization since I’m moving only one file and I don’t need to list out the remote files. Due to the shell parameter expansion, a local file like <code>20240323T102000-0500.mp4</code> will be put under <code>&lt;folder&gt;/20240323/10/20240323T102000-0500.mp4</code> on the remote. Essentially, I’m organizing the recording files into dates and then hours.</p>
<p>A few comments about <code>inotify-tools</code>. It is mostly a Linux tool, so alternatives are needed on other platforms. Here is one <a href="https://github.com/nowsecure/fsmon">example</a>, but I haven’t used it myself.</p>
<h3>Step 6: Set up Recording</h3>
<p>Finally, set up the actual recording. I use FFmpeg to record the secondary low-resolution RTSP stream from the security camera. The command line parameters are tweaked in a way that the recording will be in 5-minute chunks. Below is the command I use. Note that I execute this command under the <code>&lt;watch_folder&gt;</code> as mentioned in Step 5.</p>
<pre><code class="hljs language-bash">ffmpeg \
  -nostdin \
  -hide_banner \
  -nostats \
  -loglevel error \
  -i <span class="hljs-string">&#x27;rtsp://&lt;user&gt;:&lt;pw&gt;@&lt;camera_ip&gt;:&lt;port&gt;/path/to/low/resolution/stream&#x27;</span> \
  -map 0 \
  -vcodec copy \
  -acodec aac \
  -b:a 48k \
  -f segment \
  -segment_time 300 \
  -segment_atclocktime 1 \
  -segment_format mp4 \
  -reset_timestamps 1 \
  -strftime 1 \
  %Y%m%dT%H%M%S%z.mp4 &amp;
</code></pre>
<p><a href="https://ffmpeg.org/ffmpeg.html">FFmpeg documentation</a> has the description for all the parameters. Here I’ll try to briefly explain what is going on from an FFmpeg newbie’s perspective.</p>
<ul>
<li><code>-nostdin</code>: Turn off the standard input so that FFmpeg can run as a background job.</li>
<li><code>-hide_banner</code>: Turn off printing the banner which includes FFmpeg build options and versions of its internal libraries.</li>
<li><code>-nostats</code>: Turn off the progress report. The progress report is supposed to constantly refresh on the same line, but for some reason it doesn’t work on my old Android phone. Instead, it outputs a new line ever time it refreshes.</li>
<li><code>-loglevel error</code>: Set log level to error. In my setup, I constantly get the warning about non-monotonous DTS, but I couldn’t find a way to resolve it.</li>
<li><code>-i ...</code>: The input file for FFmpeg. Here I’m using the RTSP stream as the input file.</li>
<li><code>-map 0</code>: Map all streams (video, audio, and technically subtitle) from the input file to the output file.</li>
<li><code>-vcodec copy</code>: Copy the video stream from input to output as opposed to decoding and encoding it. This saves a lot of CPU power.</li>
<li><code>-acodec aac</code>: Use AAC when encoding the audio stream. I cannot use <code>copy</code> as in the <code>-vcodec</code> option because the audio stream is encoded in PCM (<code>pcm_alaw</code>) and FFmpeg doesn’t support it in the MP4 format.</li>
<li><code>-b:a 48k</code>: Set the audio bit rate to 48 kb/s. This is just to make a warning about “too many bits per frame requested” go away in my setup.</li>
<li><code>-f segment</code>: Use segments in output.</li>
<li><code>-segment_time 300</code>: Each segment is 300 seconds, i.e. 5 minutes.</li>
<li><code>-segment_atclocktime 1</code>: Start a new segment at clock time. Combined with the segment time above, it means that a new segment will be started at 00:00, 00:05, 00:10, etc.</li>
<li><code>-segment_format mp4</code>: Use MP4 as the segment format.</li>
<li><code>-reset_timestamp 1</code>: Reset the timestamp at the beginning of each segment. I find that SMPlayer doesn’t play the segment files well without this.</li>
<li><code>-strftime 1</code>: Enable the <code>strftime()</code> format in the output file names.</li>
<li><code>%Y%m%dT%H%M%S%z.mp4</code>: My custom output file names. The result is like <code>20240323T102000-0500.mp4</code>.</li>
</ul>
<h3>Put It Together</h3>
<p>Combined all the above, the end result is like this. I can watch the security camera’s live view on any platform when I’m at home. A recording file created at time <code>T</code> will contain the security camera footage between <code>T</code> and <code>T+5min</code>, and it will be uploaded to the cloud storage several seconds after <code>T+5min</code>. I can share those files to the small group of people. From their point of view, they are viewing a chunked but real time stream with a 5-minute delay. I can also enable VPN on my router to watch the live view when I’m out somewhere. The recording files on the cloud are also useful for checking out what happened back in time. When everything runs smoothly, the computing device won’t hold any recording file for a long period of time.</p>
<h3>Limitations</h3>
<p>Last but not the least, my setup does have some limitations and known issues. I have to manually delete the recording files on the cloud storage for the time being. I just haven’t had the chance to write another script or cron job to do that automatically. Also, there was one time in the past several months where the Termux process just exited abruptly without any trace, causing several hours of down time for the entire setup until I restarted it. After that incident, I let Termux to acquire the wake lock, and I haven’t seen the issue since then. All in all, I find my setup to be an acceptable solution for now.</p>
<hr>
<p><strong>Updates on 2024-07-05:</strong> I’ve done some troubleshooting for the known issues and new issues found over the past few months. I’ve updated this post and here is a quick change log.</p>
<ol>
<li>Remove the <code>-rtsp_transport tcp</code> argument for the FFmpeg command. I needed it previously, but now I’m seeing lags in the recordings. Getting rid of it fixed the issue. The issue might be due to a firmware upgrade on the security camera.</li>
<li>Use <code>inotify-tools</code> instead of a cron job. I didn’t expect to see <code>inotify-tools</code> on Termux for Android 6. My previous approach will upload premature files containing only the 48-byte MP4 header due to a race condition. Glad that I checked <code>inotify-tools</code> this time.</li>
<li>And of course, fix a critical typo in one of the parameter names for the FFmpeg command.</li>
</ol>
<hr>
<p><strong>Updates on 2025-05-25:</strong> See <a href="/posts/rclone-in-termux-on-android-6/">this post</a> on lessons learned for running Rclone in Termux on Android 6.</p>
]]></description><link>posts/sharing-security-camera-view</link><guid isPermaLink="true">posts/sharing-security-camera-view</guid></item><item><title><![CDATA[Repurposing an Old Android Phone]]></title><description><![CDATA[<p>Recently, I’ve repurposed an old Android phone as a tiny home server.</p>
<p>I have a Nexus 5 that still functions well. For various reasons I didn’t trade it in over all these years, and the trade-in value for it has long gone down to zero. It has always been my emergency phone in the case that my current main device is broken. I haven’t found a better use for it until now, when I need a tiny home server to record the footage from an IP security camera. I mean, a Raspberry Pi and the like would be a perfect fit, but why not give it a try on Nexus 5?</p>
<p>Note that some steps I did in this blog post may not be the best practices from the security perspective. I only need some computing power within my private home network, so it’s a tradeoff that makes sense in my use case.</p>
<p>First things first, I did a factory reset on the Nexus 5 to restore the stock 6.0.1 firmware. I didn’t sign in any Google account as I figured I wouldn’t need any Google service or software update. This lead to an interesting problem where the Chrome browser version was too low to render some webpages including the release page on GitHub. I still needed a newish browser anyway, so I downloaded the Firefox APK from the official <a href="https://github.com/mozilla-mobile/firefox-android">Firefox for Android repo</a> by looking up the direct download link from another (more modern) device and manually typing it in the Nexus 5’s Chrome. I used the armeabi-v7a build, and it worked nicely.</p>
<p>I’ve heard different suggestions on the phone battery, especially on the case like mine where I would plug the phone in all day long. I decided to follow the suggestion to limit the battery level to around 75%. To that end, I installed <a href="https://github.com/topjohnwu/Magisk">Magisk</a>, rooted the device following Magisk documentation, and installed the <a href="https://github.com/VR-25/acc">Advanced Charging Controller (ACC) module</a> inside the Magisk app. I didn’t bother tweaking the parameters for ACC. Maybe some other time.</p>
<p>Now it’s time for the main course. The key for repurposing the device is <a href="https://github.com/termux/termux-app">Termux</a>, which is a terminal emulator that supports extensible packages. As mentioned in its README, Termux actually dropped the support for Android 5 and 6 previously, but then later added it back without support. I gave it a try and it worked perfectly. The nicest thing in my opinion was that the Termux app could keep running even after the screen was locked. For my use case, I needed FFmpeg and cron job support. I installed the former via:</p>
<pre><code class="hljs language-bash">pkg install ffmpeg
</code></pre>
<p>Understandably, the FFmpeg version was not the latest and greatest, but the version there, 4.2.1, was good enough.</p>
<p>Termux had built-in support for cron jobs. I just needed to use the old syntax:</p>
<pre><code class="hljs language-bash">busybox crontab -e
busybox crond
</code></pre>
<p>Now I had everything I needed to record the footage. It worked but with some issues. That will be a different story next time.</p>
<p>For what it’s worth, Termux on Android 6 also has Node.js versions 12 and 13, as well as Python versions 2.7 and 3.8.</p>
]]></description><link>posts/repurposing-an-old-android-phone</link><guid isPermaLink="true">posts/repurposing-an-old-android-phone</guid></item><item><title><![CDATA[Text Replacement for Small Screens]]></title><description><![CDATA[<p>I’ve found that using text replacement is an acceptable way to write code on small mobile screens.</p>
<p>Recently, I had to write some code on a small phone screen. Typing code on such screens is a pain since symbols are hard to find and auto-corrections always get in the way. On Android, the problem may be mitigated by a good keyboard such as <a href="https://github.com/klausw/hackerskeyboard">Hacker’s Keyboard</a>, but I couldn’t find its alternatives on iOS.</p>
<p>Then I realized that I could use text replacement. Different platforms may call it differently, but essentially the idea is to expand a “shortcut” into a relatively long phrase automatically upon typing. On iOS 16 it’s under <code>Settings &gt; General &gt; Keyboard &gt; Text Replacement</code>. For starters, some shortcuts for Java code could be:</p>
<ul>
<li><code>fori -&gt; for (int i=0;i&lt;n;i++) {</code></li>
<li><code>intmax -&gt; Integer.MAX_VALUE</code></li>
<li><code>sysout -&gt; System.out.println(</code></li>
</ul>
<p>In this way, I can speed up typing code with the native iOS keyboard. It’s not perfect for sure, but it did improve my coding experience on a small screen.</p>
<p>In general, I think text replacement is an underrated feature. I’m also using <a href="https://espanso.org/">Espanso</a> to save some typing on non-mobile platforms.</p>
]]></description><link>posts/text-replacement-for-small-screens</link><guid isPermaLink="true">posts/text-replacement-for-small-screens</guid></item><item><title><![CDATA[Be Aware of Pre-Configuration in VPS]]></title><description><![CDATA[<p>Be aware of the pre-configuration that may happen on a virtual private server (VPS) instance.</p>
<p>Recently, the tipping point has come for me to set up my own RSS aggregator. I’ve paid for a cheap VPS to host a <a href="https://www.freshrss.org/">FreshRSS</a> instance. I just spun up a Docker container for FreshRSS, and I was immediately faced with some network issues. I used the technique in <a href="/posts/quick-file-transfor-in-private-network/">this post</a> to confirm that the host machine and the firewall were working as expected, so I was totally puzzled.</p>
<p>Eventually, I came to <a href="https://forums.docker.com/t/docker-bridge-networking-does-not-work-in-ubuntu-22-04/136326">this post</a> where folks were talking about Docker network and some network pre-configuration. It applies to my situation as well. Essentially, my VPS provider uses <code>cloud-init</code> to pre-configure <code>netplan</code>, which in turn is in charge of configuring network settings on Ubuntu 22.04 LTS during the boot. My limited understanding here is that the settings it generates forces all network interfaces to go through DHCP, which affects Docker’s default bridge networking. As mentioned in that post, one solution is to only force the network interfaces with names like <code>en*</code>, so Docker’s <code>docker0</code> won’t be included.</p>
<p>Following the lead, I also found some other pre-configured settings here and there on the host. I guess that’s the norm for VPS now. The lesson learned here is that if I don’t know why some behavior is unexpected on a VPS host I should check if it’s pre-configured in some way.</p>
]]></description><link>posts/be-aware-of-pre-configuration-in-vps</link><guid isPermaLink="true">posts/be-aware-of-pre-configuration-in-vps</guid></item><item><title><![CDATA[Poppler Utilities for Manipulating PDF]]></title><description><![CDATA[<p>I’ve just realized that <a href="https://poppler.freedesktop.org/">Poppler</a> has command line utilities and they are perfect for manipulating PDF files.</p>
<p>I was trying to extract a few pages from various PDF documents and combine them into a single file for easier double-sided printing. I had used <a href="https://www.pdflabs.com/tools/pdftk-the-pdf-toolkit/">PDFtk</a> before, so I typed in <code>pdf</code> hoping that the shell autocompletion can remind me what the executable is. Several candidates popped out without anything that looked like PDFtk. However, I noticed one named <code>pdfseparate</code>. Looking at its help information, apparently, it’s part of the Poppler library. I’ve always considered Poppler as a PDF generation library due to my usage of PDFLaTex, but now I see that it has command line utilities as well. It’s a pleasant surprise that the utilities are very easy to use.</p>
<p>For example, I used the following command to extract the second page from a PDF document. Note that <code>-f</code> and <code>-l</code> indicate first and last pages to extract, inclusively.</p>
<pre><code class="hljs language-bash">pdfseparate -f 2 -l 2 input.pdf output.pdf
</code></pre>
<p>Similarly, I used the following command to combine the extracted pages into a single PDF file:</p>
<pre><code class="hljs language-bash">pdfunite input1.pdf input2.pdf input3.pdf output.pdf
</code></pre>
<p>Later, I found a <a href="https://en.wikipedia.org/wiki/Poppler_(software)#poppler-utils">wiki page</a> for brief descriptions on all the Poppler utilities.</p>
<p>I wish that I had known this earlier, but better late than never.</p>
]]></description><link>posts/poppler-utilities-for-manipulating-pdf</link><guid isPermaLink="true">posts/poppler-utilities-for-manipulating-pdf</guid></item><item><title><![CDATA[Quick File Transfer in Private Network]]></title><description><![CDATA[<p>Recently, I’ve realized a quick way to transfer some files within a private network.</p>
<p>Occasionally, I need to transfer some files from a MacBook to a Linux/Windows desktop. They are in the same private network. I always used a USB flash drive, and it worked just fine. The other day, I suddenly realized that a simple HTTP server could help since its directory listing is probably turned on by default. In other words, I can start an HTTP server at a certain directory on the MacBook, and then I can browser that directory’s content using the MacBook’s IP address in the desktop’s browser. The fastest way I could think of starting such an HTTP server was to use the Python built-in one:</p>
<pre><code class="hljs language-bash">python3 -m http.server
</code></pre>
<p>It worked pretty well. If I need to transfer multiple files, I can always compress them into a single file first so that I don’t have to download them one by one.</p>
<p>The same idea can be applied to other languages and runtime environments as long as there is an easy-to-start HTTP server and it can access local files.</p>
]]></description><link>posts/quick-file-transfor-in-private-network</link><guid isPermaLink="true">posts/quick-file-transfor-in-private-network</guid></item><item><title><![CDATA[Downgrading Arch Linux Kernel]]></title><description><![CDATA[<p>Yesterday, I had to match the kernel and the display driver versions when downgrading the Arch Linux kernel. Now I’m jotting it down as a memo.</p>
<p>I did a system upgrade yesterday on my Arch Linux install. The kernel was updated to version 5.16.16. After rebooting the machine, I noticed that the WiFi connection kept dropping and reconnecting, and it was very hard for me to open any website. I looked it up on my phone and found that there was <a href="https://bugzilla.kernel.org/show_bug.cgi?id=215703">this bug</a> in recent kernel changes. Versions 5.16.14 and below seem unaffected.</p>
<p>After downgrading the <code>linux</code> package to 5.16.14, I couldn’t see the graphical login screen. This is not a surprise for me. I’ve seen this during my previous kernel upgrading, and reinstalling the nVidia display driver worked. I did that, but this time it didn’t work. Now I’m a bit surprised. I vaguely remember that the kernel version and the display driver version need to be “compatible”, although the exact reason has always been a puzzle for me. Let me downgrade <code>nvidia-*</code> packages to an arbitrary previous version. Uh, no luck.</p>
<p>I tried looking at various logs, but I couldn’t find a proper workaround. Now it’s time to roll back to the exact versions before the system upgrade. I looked at the pacman log at <code>/var/log/pacman.log</code>, and wrote down the versions for packages <code>linux</code>, <code>nvidia</code>, <code>nvidia-utils</code>, and <code>nvidia-settings</code>. Then I used <code>downgrade</code> to install those exact versions. It’s nice that <code>downgrade</code> can use local cached packages, and it also shows which versions were installed previously. This time it finally worked.</p>
<p>As a side note, I think the DKMS version of the nVidia display driver <em>may not</em> have this “compatible version” issue. That said, I switched away from it a long time ago, and I forgot why.</p>
]]></description><link>posts/downgrading-kernel</link><guid isPermaLink="true">posts/downgrading-kernel</guid></item><item><title><![CDATA[My Mac Productivity Setup]]></title><description><![CDATA[<p>Here is my productivity setup on a Mac. It is just a personal memo, not an endorsement or an advertisement.</p>
<h3>Tools and Apps</h3>
<p>Here is a brief list of some productivity tools. Further sections will discuss combining them to achieve powerful workflows and setups.</p>
<ul>
<li><a href="https://github.com/timche/gmail-desktop">Gmail Desktop</a>: a third-party Gmail client.</li>
<li><a href="https://github.com/rxhanson/Rectangle">Rectangle</a>: resize windows using keyboard shortcuts.</li>
<li><a href="https://github.com/pqrs-org/Karabiner-Elements">Karabiner-Elements</a>: keyboard customization, especially useful for remapping keys and define global hotkeys.</li>
<li><a href="https://github.com/swiftbar/SwiftBar">SwiftBar</a>: menu bar customization tool, similar to <a href="https://github.com/matryer/xbar">BitBar</a>.</li>
<li><a href="https://github.com/archagon/sensible-side-buttons">SensibleSideButtons</a>: enable system-wide navigation for mouse side buttons.</li>
<li><a href="https://github.com/glushchenko/fsnotes">FSNotes</a>: notes manager, similar to <a href="https://notational.net/">Notational Velocity</a> and <a href="https://brettterpstra.com/projects/nvalt/">nvALT</a>.</li>
<li><a href="https://github.com/Mortennn/Dozer">Dozer</a>: hide menu bar icons.</li>
<li><a href="https://github.com/Clipy/Clipy">Clipy</a>: clipboard manager.</li>
<li><a href="https://github.com/Caldis/Mos">Mos</a>: smooth scrolling.</li>
<li><a href="https://github.com/sfsam/Itsycal">Itsycal</a>: menu bar calendar.</li>
</ul>
<h3>Notes and Todo List (and CPU Temperature)</h3>
<p>I use FSNotes for all my notes, including a special text file named <code>Todo.txt</code>. This file is a list of my todo tasks. I’ve written a simple SwiftBar plugin to show the number of lines in that file, indicating how many tasks are unfinished. If the plugin refreshes every 15 seconds, why not let it collect other data as well? So the actual plugin I use also captures the CPU temperature through <a href="https://github.com/Chris911/iStats">iStats</a>. The plugin looks like this:</p>
<pre><code class="hljs language-bash"><span class="hljs-meta">#!/bin/bash</span>

<span class="hljs-built_in">export</span> GEM_HOME=~/.gem
temp=$(<span class="hljs-built_in">printf</span> <span class="hljs-string">&quot;%.0f&quot;</span> `<span class="hljs-variable">$GEM_HOME</span>/bin/istats cpu temp --value-only`)
speed=$(<span class="hljs-variable">$GEM_HOME</span>/bin/istats fan speed --no-graphs --no-labels | <span class="hljs-built_in">head</span> -n 1)

unread=`awk <span class="hljs-string">&#x27;END{print NR}&#x27;</span> ~/path/to/Todo.txt`
<span class="hljs-keyword">if</span> [ <span class="hljs-variable">$unread</span> -gt 0 ]
<span class="hljs-keyword">then</span>
    todo=<span class="hljs-string">&quot;:checkmark.square: <span class="hljs-variable">$unread</span>&quot;</span>
<span class="hljs-keyword">else</span>
    todo=<span class="hljs-string">&quot;:checkmark.seal:&quot;</span>
<span class="hljs-keyword">fi</span>

<span class="hljs-built_in">echo</span> -e <span class="hljs-string">&quot;:cpu: <span class="hljs-variable">$temp</span>\xc2\xb0C    <span class="hljs-variable">$todo</span>&quot;</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;---&quot;</span>
<span class="hljs-built_in">echo</span> <span class="hljs-variable">$speed</span>
</code></pre>
<p>Note that those colon-wrapped words like <code>:checkmark.square:</code> are referring to <a href="https://developer.apple.com/sf-symbols/">SF Symbols</a>, as mentioned in SwiftBar <a href="https://github.com/swiftbar/SwiftBar#parameters">documentation</a>.</p>
<h3>Global Hotkeys</h3>
<p>I like to define some global hotkeys so that I can switch to specific applications quickly. For example, I want to press Command+3 to switch to Gmail Desktop. I didn’t find a good open source app to set global hotkeys on a Mac, so I used AppleScript and Karabiner-Elements as a workaround.</p>
<p>First of all, I use the following script to show and hide a specific application. Note that it will launch the application if it’s not already launched.</p>
<pre><code class="hljs language-AppleScript"><span class="hljs-keyword">tell</span> <span class="hljs-built_in">application</span> <span class="hljs-string">&quot;System Events&quot;</span>
    <span class="hljs-keyword">set</span> names <span class="hljs-keyword">to</span> <span class="hljs-built_in">name</span> <span class="hljs-keyword">of</span> <span class="hljs-built_in">application</span> processes
    <span class="hljs-keyword">if</span> names <span class="hljs-keyword">contains</span> <span class="hljs-string">&quot;Gmail Desktop&quot;</span> <span class="hljs-keyword">then</span>
        <span class="hljs-keyword">tell</span> <span class="hljs-built_in">application</span> process <span class="hljs-string">&quot;Gmail Desktop&quot;</span>
            <span class="hljs-keyword">if</span> <span class="hljs-built_in">frontmost</span> <span class="hljs-keyword">is</span> <span class="hljs-literal">true</span> <span class="hljs-keyword">then</span>
                <span class="hljs-keyword">set</span> visible <span class="hljs-keyword">to</span> <span class="hljs-literal">false</span>
            <span class="hljs-keyword">else</span>
                <span class="hljs-keyword">tell</span> <span class="hljs-built_in">application</span> <span class="hljs-string">&quot;Gmail Desktop&quot;</span> <span class="hljs-keyword">to</span> <span class="hljs-built_in">activate</span>
            <span class="hljs-keyword">end</span> <span class="hljs-keyword">if</span>
        <span class="hljs-keyword">end</span> <span class="hljs-keyword">tell</span>
    <span class="hljs-keyword">else</span>
            <span class="hljs-keyword">tell</span> <span class="hljs-built_in">application</span> <span class="hljs-string">&quot;Gmail Desktop&quot;</span> <span class="hljs-keyword">to</span> <span class="hljs-built_in">activate</span>
    <span class="hljs-keyword">end</span> <span class="hljs-keyword">if</span>
<span class="hljs-keyword">end</span> <span class="hljs-keyword">tell</span>
</code></pre>
<p>Then I define some “<a href="https://karabiner-elements.pqrs.org/docs/manual/configuration/configure-complex-modifications/">complex rules</a>“ in Karabiner-Elements:</p>
<pre><code class="hljs language-json"><span class="hljs-punctuation">{</span>
    <span class="hljs-attr">&quot;title&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;Global Hotkey&quot;</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">&quot;rules&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span>
        <span class="hljs-punctuation">{</span>
            <span class="hljs-attr">&quot;description&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;Global hotkeys to launch applications&quot;</span><span class="hljs-punctuation">,</span>
            <span class="hljs-attr">&quot;manipulators&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span>
                <span class="hljs-punctuation">{</span>
                    <span class="hljs-attr">&quot;type&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;basic&quot;</span><span class="hljs-punctuation">,</span>
                    <span class="hljs-attr">&quot;from&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
                        <span class="hljs-attr">&quot;key_code&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;3&quot;</span><span class="hljs-punctuation">,</span>
                        <span class="hljs-attr">&quot;modifiers&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
                            <span class="hljs-attr">&quot;mandatory&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span>
                                <span class="hljs-string">&quot;command&quot;</span>
                            <span class="hljs-punctuation">]</span>
                        <span class="hljs-punctuation">}</span>
                    <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
                    <span class="hljs-attr">&quot;to&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span>
                        <span class="hljs-punctuation">{</span>
                            <span class="hljs-attr">&quot;shell_command&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;osascript ~/path/to/above.scpt&quot;</span>
                        <span class="hljs-punctuation">}</span>
                    <span class="hljs-punctuation">]</span>
                <span class="hljs-punctuation">}</span>
            <span class="hljs-punctuation">]</span>
        <span class="hljs-punctuation">}</span>
    <span class="hljs-punctuation">]</span>
<span class="hljs-punctuation">}</span>
</code></pre>
<p>Note that the script file here is the compiled one, as <a href="/posts/compile-applescript-for-performace">mentioned previously</a>.</p>
]]></description><link>posts/my-mac-productivity-setup</link><guid isPermaLink="true">posts/my-mac-productivity-setup</guid></item><item><title><![CDATA[Converting Markdown Files]]></title><description><![CDATA[<p>Recently, I’ve found an acceptable workflow for converting a markdown file to a non-markdown-fashioned document.</p>
<p>I’ve been using markdown format for writing documents lately. I use heading level one for title and level two for “the top-level sections”. One problem is that sometimes I need to convert the document to a non-markdown-fashioned format like Google Doc. I tried rendering the markdown file in styled HTML and then copy-pasting the result in Google Doc directly. The result looks OK, but the major problem is that the heading levels are off by one, and I have to manually change them.</p>
<p>Then I found <code>pandoc</code>. It is not rarely known per se, but I found a very useful option called <code>--shift-heading-level-by=-1</code>. This solves my problem mentioned above exactly. All heading levels are shifted by one, and the heading level one used as the title in my markdown document is removed.</p>
<p>Now my workflow looks like this. I write in markdown format, use <code>pandoc</code> to convert it to raw HTML format with headings shifted by one, and finally copy-paste the HTML from browser to Google Doc. Google Doc can recognize and match most of the HTML tags to its built-in styles, except for the code style. This is totally acceptable for me before I encounter better solutions.</p>
]]></description><link>posts/converting-markdown-files</link><guid isPermaLink="true">posts/converting-markdown-files</guid></item><item><title><![CDATA[Ignoring Local Packages in Upgrade]]></title><description><![CDATA[<p>I’ve just found a way to ignore local packages when upgrading packages using Yay in Arch Linux.</p>
<p>I use Yay for managing packages on Arch Linux. As mentioned in <a href="/posts/switching-to-arch-linux-tex-live/">this post</a>, I use <code>tllocalmgr</code> to manually install some CTAN packages, mostly fonts. The consequence is that these packages only exist on my local machine. Every time I upgrade packages in Yay, it will prompt me that those packages don’t have corresponding AUR packages. That isn’t necessarily a bad thing, but I just want to hide it.</p>
<p>I came across <a href="https://github.com/Jguer/yay/issues/780">this issue</a> the other day. This afternoon, I finally decided to give it a try. All I did was to uncomment the <code>IgnorePkg</code> setting in <code>/etc/pacman.conf</code>, and added a package name pattern, so that it looked like this:</p>
<pre><code class="hljs language-properties"><span class="hljs-attr">IgnorePkg</span> = <span class="hljs-string">texlive-local-*</span>
</code></pre>
<p>It worked like a charm. Now, Yay no longer prompts missing AUR packages. Fortunately, we don’t have official or AUR packages staring with <code>texlive-local-</code> at the moment.</p>
<p>This post is more like a memo to me as I know one day I will wonder where or how I did this.</p>
]]></description><link>posts/ignoring-local-packages-in-upgrade</link><guid isPermaLink="true">posts/ignoring-local-packages-in-upgrade</guid></item><item><title><![CDATA[Compile AppleScript for Performance]]></title><description><![CDATA[<p>Recently, I’ve found that compiling an AppleScript file may help with its performance.</p>
<p>I have this arguably weird workflow on a Mac where I can press Command+2 and Command+3 to switch to specific applications. I didn’t find any open source solution for it, so I had to create some custom AppleScripts and bind them to global hotkeys in Karabiner-Elements. The setup worked for me until I upgraded to macOS Catalina, where the performance of the scripts was noticeably degraded. I had to wait for a few seconds before the application switching to the foreground.</p>
<p>I have to use the command <code>osascript</code> to run my custom AppleScript when the designated hotkey is triggered. I was reading its manual for some inspirations, and I actually found one at the bottom of the man page: <code>osacompile</code>, which can compile AppleScripts. I gave it a try using <code>osacompile -x</code> which is said to save the resulting script as execute only.  It didn’t let me down. The execution performance of my script was improved drastically.</p>
<p>This is not the end of the story though. I’ve noticed that although the performance is improved in general, it doesn’t eliminate the problem. Sometimes I still have to wait for a while for the script to finish. My guess is that something about AppleScript is changed in macOS Catalina, like the script interpretor or the built-in script functions. Maybe something about intercepting keyboard inputs is also changed so that Karabiner-Elements is slowed down.</p>
<p>That said, the general idea here may be helpful in certain cases. Maybe the next time when I’m dealing with scripts, I’ll remember to check if there is a way to compile them.</p>
]]></description><link>posts/compile-applescript-for-performace</link><guid isPermaLink="true">posts/compile-applescript-for-performace</guid></item><item><title><![CDATA[Switching to Arch Linux TeX Live]]></title><description><![CDATA[<p>I’ve just switched my TeX Live distribution from the official one to the one on the Arch Linux repository.</p>
<p>I was just updating the packages routinely on Arch Linux, and I noticed that Texmaker now requires <code>texlive-core</code>, which in turn requires <code>texlive-bin</code>. This was problematic for me since I had already installed the official TeX Live distribution. This not only took more disk space, but also broke the functionality. For example, I was no longer able to compile my resume. My guess was that the new install somehow affected the TeX Live file tree and some style files were considered missing.</p>
<p>Up to this point, I had two options. I could uninstall Texmaker and its TeX Live dependencies, then reinstall Texmaker from another source and use it with the existing official TeX Live distribution on my machine. Alternatively, I could uninstall the official distribution and switch to the Arch Linux packaged version of TeX Live. Either option should work. I was just curious about the latter option, so I chose to give it a try. In addition, I had already changed my resume template to <a href="https://github.com/zzpxyx/two-column-resume">my custom one</a> long time ago, and it no longer needed unofficial fonts or stuff, so switching to another TeX Live distribution would only require installing missing CTAN packages.</p>
<p>First of all, I uninstalled the official distribution by:</p>
<pre><code class="hljs language-shell">tlmgr uninstall --all
</code></pre>
<p>There were some errors about write permission, but I did some quick check and the errors seemed benign. Then I installed <code>texlive-latexextra</code> for some commonly used LaTeX packages. After that, I tried to compile my resume and saw that some fonts were missing as expected. A quick search showed that I should install <code>texlive-fontsextra</code>. It would take over 1 GB of disk space since it included tons of fonts. I’m not a fan of this, so I chose to use <a href="https://wiki.archlinux.org/index.php/TeX_Live#tllocalmgr">tllocalmgr</a> to manually install missing CTAN packages. After some trial and error, I ended up with installing these CTAN packages: <code>raleway</code>, <code>mweights</code>, <code>droid</code>, <code>fontawesome</code>, and <code>ly1</code>. However, no PDF file was generated at the end of compile, and Texmaker didn’t report any obvious error. The log did show that some fonts were missing. <code>tllocalmgr</code> mentioned that I should check its log and modify the <code>updmap.cfg</code> file as needed. After some trial and error again, I found that I would need to add the font maps like below for the fonts mentioned in the <code>tllocalmgr</code> log file.</p>
<pre><code class="hljs language-shell">updmap-sys --enable Map=Raleway.map
updmap-sys --enable Map=droidsans.map
updmap-sys --enable Map=droidsansmono.map
updmap-sys --enable Map=droidserif.map
updmap-sys --enable Map=fontawesome.map
</code></pre>
<p>I’m still a newbie for TeX Live, so what I did here may not be the best practice. After that, the compile worked and generated a PDF file.</p>
<p>I used <a href="https://pypi.org/project/diff-pdf-visually/">diff-pdf-visually</a> to compare the two PDF files from the two TeX Live distributions, and the tool reported no visual difference. I would take this as a sign of victory. If I’m understanding it correctly, I would no long need to upgrade TeX Live like <a href="../upgrading-tex-live">my previous post</a>.</p>
<p><strong>Updates on 2023-06-19:</strong> The TeX Live packages on Arch Linux have been <a href="https://archlinux.org/news/tex-live-package-reorganization/">reorganized</a>, and <code>tllocalmgr</code> no longer works. At this time, I’m using the <code>tlmgr</code> fix in the <a href="https://wiki.archlinux.org/title/TeX_Live#tlmgr">ArchWiki</a> for installing additional packages from CTAN.</p>
]]></description><link>posts/switching-to-arch-linux-tex-live</link><guid isPermaLink="true">posts/switching-to-arch-linux-tex-live</guid></item><item><title><![CDATA[Reviving an Old MP3 Player]]></title><description><![CDATA[<p>I’ve finally had the chance to jot down my adventure in reviving a very old MP3 player.</p>
<p>The player is made by Philips. I think the model number is something like SA2SPK02S/93. It is almost 10 years old, so by the time I tried to turn it on, nothing appeared on the screen. Not surprised at all.</p>
<p>Apparently the battery is already depleted and possibly over-discharged. It has a USB port for charging, so I connected it to my desktop machine. It appeared to be charging, and showed full battery really quickly. I disconnected it and then tried to turn it on, but it went into a weird boot loop. It showed that the battery was low, and then refused to do anything else. I also tried to charge it over a long period of time, but it didn’t help.</p>
<p>I was about to give up before I got curious about its management software, Philips Songbird. Technically, this MP3 player can function very well on its own. I was just curious on what the management software was for. After fighting with some compatibility issues, I finally got the software running, and there was a firmware update option. No new firmware available. Not surprised at all, either. However, I could reinstall the existing firmware. I immediately gave it a try because why not?</p>
<p>That turned out to be the correct solution. After reinstalling the firmware, my desktop machine recognized the device, and charging was back to normal. Sometime later I gave the MP3 player a spin, and it worked well. Nice.</p>
<p>My guess is that somehow the internal battery level checker, hardware or software, had some errors. The MP3 player thought that it was fully charged, but actually only 2% or so. The boot process was disrupted due to low battery, which caused all kinds of weird issues. Finally, reinstalling the firmware calibrated the batter level checker or something, and made all problems go away.</p>
<p>I actually did this quite a while ago. Now, at the time of writing this post, the battery of that MP3 player is depleted again. Interestingly, this time everything works. The device is recognized by my desktop immediately, and charging seems fine. Maybe my guess above is not that bad.</p>
<p>To sum it up, reinstalling the firmware might be helpful when dealing with a dead battery. This probably doesn’t work every time, but it’s worth a try.</p>
]]></description><link>posts/reviving-an-old-mp3-player</link><guid isPermaLink="true">posts/reviving-an-old-mp3-player</guid></item><item><title><![CDATA[YAML Schema for Organizing Data]]></title><description><![CDATA[<p>I have an idea about using YAML Schema to organize data.</p>
<p>I came to this idea the other day when I was asked whether I had a certain vaccine in the past. All of a sudden, I wish I had organized my personal medical data in a structured way so that I could easily look it up. For easier discussion, let’s use personal medical data as an example here.</p>
<p>First of all, I choose texts rather than database or any binary format because I want it to be readable and easy to maintain. I also want it to be organized, so I choose the YAML format where it’s kind of both human- and machine-readable, yet doesn’t require a lot of skeleton characters like JSON. On top of that, I add YAML Schema so that the data YAML file can be validated. It’s also useful for auto-completion in editors that supports it. YAML Schema is based on JSON Schema, which is still work in progress. YAML Schema is also written in JSON at this moment.</p>
<p>Here is a snippet of YAML Schema that I use to organize my immunization history:</p>
<pre><code class="hljs language-JSON"><span class="hljs-attr">&quot;immunization&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
  <span class="hljs-attr">&quot;description&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;Immunization history.&quot;</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">&quot;type&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;array&quot;</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">&quot;items&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
    <span class="hljs-attr">&quot;type&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;object&quot;</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">&quot;properties&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
      <span class="hljs-attr">&quot;date&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
        <span class="hljs-attr">&quot;description&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;Date of the immunization.&quot;</span><span class="hljs-punctuation">,</span>
        <span class="hljs-attr">&quot;type&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;string&quot;</span>
      <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
      <span class="hljs-attr">&quot;name&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
        <span class="hljs-attr">&quot;description&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;Immunization name.&quot;</span><span class="hljs-punctuation">,</span>
        <span class="hljs-attr">&quot;type&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;string&quot;</span>
      <span class="hljs-punctuation">}</span>
    <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">&quot;required&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-string">&quot;date&quot;</span><span class="hljs-punctuation">,</span> <span class="hljs-string">&quot;name&quot;</span><span class="hljs-punctuation">]</span>
  <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">&quot;uniqueItems&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">true</span></span>
<span class="hljs-punctuation">}</span>
</code></pre>
<p>Basically, I’m defining an array named <code>immunization</code>. Each element of the array is an object that has two properties, <code>date</code> and <code>name</code>, which are both strings. I don’t think we have date-time type at the moment, so I have to use strings for dates. I’m also declaring that the elements should be unique in the array.</p>
<p>With the above schema, I can add and validate the following section in my data YAML file:</p>
<pre><code class="hljs language-YAML"><span class="hljs-attr">immunization:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">date:</span> <span class="hljs-number">2019</span><span class="hljs-string">/12/13</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">some</span> <span class="hljs-string">vaccine</span> <span class="hljs-number">1</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">date:</span> <span class="hljs-number">2019</span><span class="hljs-string">/12/14</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">some</span> <span class="hljs-string">vaccine</span> <span class="hljs-number">2</span>
</code></pre>
<p>Similarly, I can add definitions for surgery history and other medical information. For complete syntax about YAML Schema and JSON Schema, see <a href="https://json-schema-everywhere.github.io/yaml">here</a>.</p>
<p>I’m using VS Code with the YAML plugin. It takes a bit of time to configure, but after that, I get the auto-completion when I’m editing the data YAML file.</p>
<p>Finally, it’s worth pointing out that although the example here is for personal medical data, the idea of using YAML Schema can be applied to other structured data as well.</p>
]]></description><link>posts/yaml-schema-for-organizing-data</link><guid isPermaLink="true">posts/yaml-schema-for-organizing-data</guid></item></channel></rss>