<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Eric Park&apos;s Blog</title><description>Eric Park&apos;s blog posts</description><link>https://ericswpark.com/blog/</link><language>en</language><item><title>Internet censorship in Korea, and the eventual end</title><link>https://ericswpark.com/blog/2026/2026-01-11-korea-internet-censors/</link><guid isPermaLink="true">https://ericswpark.com/blog/2026/2026-01-11-korea-internet-censors/</guid><pubDate>Sun, 11 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This blog post is written in response to the pearl-clutching prudes and puritans
that dictate how I as an adult get to browse the goddamn Internet.&lt;/p&gt;
&lt;p&gt;Even though our country is not an authoritarian state and doesn’t have the Great
Firewall of China, &lt;a href=&quot;https://en.wikipedia.org/wiki/Internet_censorship_in_South_Korea&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;it’s still widely known for censoring the Internet&lt;/a&gt;,
something that rudely startle foreigners when they want to go on NSFW sites in
their hotel rooms.&lt;/p&gt;
&lt;p&gt;Now, if we get into the entire &lt;em&gt;should they, shouldn’t they&lt;/em&gt; thing then this blog
post will be so long you won’t read it. (I mean you probably already clicked away
but still, please pretend you’re reading. Yay, thanks for continuing.) But my take
is that censorship leads to overreach, which has &lt;strong&gt;already been proven&lt;/strong&gt; by the way
I run into roadblocks accessing sites that I as an adult have the legal right to.
So no, I don’t agree that this is the right approach and think they should fix
that, but in the meantime let me go over how they actually censor the Internet.&lt;/p&gt;
&lt;h2 id=&quot;how-they-used-to-censor-the-internet-in-korea&quot;&gt;How they (used to) censor the Internet in Korea&lt;/h2&gt;
&lt;p&gt;Traditionally, the major ISPs in Korea used to poison DNS entries like all the
other nanny states and countries that decided they could decide what’s best for
you. That means you ask for Google.com and if Google was one of the banned no-no
websites (according to some review somewhere), essentially, the ISP’s DNS servers
would respond with an IP that is not Google’s and redirect you.&lt;/p&gt;
&lt;p&gt;Of course, that was largely patched out thanks to encrypted DNS’s proliferation
and the fact that HTTPS stops your browser from loading the scary-looking
“Warning” page from the Korean government, especially if HSTS is enabled.&lt;/p&gt;
&lt;p&gt;For context, &lt;a href=&quot;http://warning.or.kr/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;this is what you would see&lt;/a&gt; if you got hit by one of
those censorship measures:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;warning-page&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1662&quot; height=&quot;810&quot; src=&quot;/_astro/warning.DN__VkpT_aH9Jb.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;Ugly as sin, all in Korean (except for the threatening “Warning” text), and some
bullshit about how they’re “legally” blocking the site as it has passed through
review of some board (read: puritans) that has determined it is either illegal
or “harmful” (유해 내용). Except “harmful” in this case extends to totally legal
mature content that little Timmy on his iPad shouldn’t be viewing, but since parents
don’t know diddly-squat about how parental controls work let us the government
do it for you!&lt;/p&gt;
&lt;p&gt;As mentioned before, people realized sending DNS requests back and forth on an
unencrypted port was a really bad idea, and we quickly patched that with things
like DNS-over-TLS and DNS-over-HTTPS. Now, Korean ISPs no longer poison DNS,
though it’s still a good idea not to use their DNS servers in case they’re doing
anything funny.&lt;/p&gt;
&lt;p&gt;So what changed?&lt;/p&gt;
&lt;h2 id=&quot;how-they-currently-censor-the-internet-in-korea&quot;&gt;How they (currently) censor the Internet in Korea&lt;/h2&gt;
&lt;p&gt;In present day, ISPs look at the actual HTTPS request.&lt;/p&gt;
&lt;p&gt;When you make a request to a server, there is some communication back and forth
to establish a TLS connection. One of the extensions to TLS is SNI, or Server
Name Indication. Basically, as web servers become more and more reverse proxies
and host various domains on one server, it became important to signal &lt;em&gt;which&lt;/em&gt;
particular domain you as a client were interested in. So the domain name would be
specified in the SNI field and the server would send over the TLS certificate
corresponding to the domain and everything would continue.&lt;/p&gt;
&lt;p&gt;But the SNI field is not encrypted, and thus Korean (and other countries’) ISPs
could sniff out which website you were trying to visit, and cut the connection.
They can’t really redirect you to the Pennywise website above, but they can
interrupt your connection by sending a reset packet.&lt;/p&gt;
&lt;h2 id=&quot;so-how-can-i-get-past-it&quot;&gt;So how can I get past it?&lt;/h2&gt;
&lt;p&gt;The easy, boring solution: use a VPN, or a proxy, etc.&lt;/p&gt;
&lt;p&gt;But it seems like a waste and a hassle, when all you need to hide is just that
single SNI field! Surely there’s a solution to that?&lt;/p&gt;
&lt;p&gt;Well, there is. Initially proposed as Encrypted Server Name Indication (ESNI),
the latest proposed standard is Encrypted Client Hello (ECH), which now encrypts
the unencrypted portions that help set up the TLS connection.&lt;/p&gt;
&lt;p&gt;With ECH, ISPs can no longer spy on what is being sent over the wire and to which
site you are connecting to.&lt;/p&gt;
&lt;h2 id=&quot;great-can-i-use-ech-now&quot;&gt;Great, can I use ECH now?&lt;/h2&gt;
&lt;p&gt;Kind of.&lt;/p&gt;
&lt;p&gt;ECH is supported in most major browsers, but servers must also support it. Support
is slowly rolling out, but until it becomes mainstream you may still run into
some sites that are being blocked by your friendly Big Brother.&lt;/p&gt;
&lt;p&gt;And for clients like &lt;code&gt;curl&lt;/code&gt;, the SSL library used must support ECH. As of right
now, the default &lt;code&gt;curl&lt;/code&gt; binary as it ships on Arch Linux does not support ECH,
for example.&lt;/p&gt;
&lt;p&gt;I ran into this problem with my Rust program, where the connection would succeed
for a given site on my browser, but fail catastrophically on my Rust client. The
dependency that I use, &lt;a href=&quot;https://github.com/seanmonstar/reqwest&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;reqwest&lt;/code&gt;&lt;/a&gt;, recently switched to
&lt;a href=&quot;https://github.com/rustls/rustls&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;rustls&lt;/code&gt;&lt;/a&gt;, which &lt;em&gt;does&lt;/em&gt; have ECH support on the client-side.
There’s no documentation on how I can use it on &lt;a href=&quot;https://github.com/seanmonstar/reqwest&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;reqwest&lt;/code&gt;&lt;/a&gt;, nor
any indication of whether it is enabled by default (so I presume no).&lt;/p&gt;
&lt;p&gt;But once ECH becomes mainstream, the only way to censor my Internet access would
be to make like China and block all requests that go over ESNI/ECH. And that
certainly wouldn’t work at all without breaking a large chunk of the Internet.&lt;/p&gt;
&lt;p&gt;I’ll certainly look forward to the day they shut down that ugly stupid warning
website.&lt;/p&gt;</content:encoded></item><item><title>SSH key updating with systemd</title><link>https://ericswpark.com/blog/2025/2025-12-22-ssh-keys-systemd-timer/</link><guid isPermaLink="true">https://ericswpark.com/blog/2025/2025-12-22-ssh-keys-systemd-timer/</guid><pubDate>Mon, 22 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This is a follow-up to a &lt;a href=&quot;/blog/2021/2021-02-01-persistent-access-with-ssh-keys/&quot;&gt;blog post I did a long while ago&lt;/a&gt;
on how to set up persistent access with SSH keys. Nowadays, it’s hard to find a
Linux system that &lt;em&gt;doesn’t&lt;/em&gt; have &lt;code&gt;systemd&lt;/code&gt;, and &lt;code&gt;crontab&lt;/code&gt; is sometimes not even
installed.&lt;/p&gt;
&lt;p&gt;So, in case you are on one of those machines, here’s how you can use that script
with a service and &lt;code&gt;systemd.timer&lt;/code&gt; unit file, that does the exact same thing as
&lt;code&gt;crontab&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;create-the-systemd-unit-files&quot;&gt;Create the &lt;code&gt;systemd&lt;/code&gt; unit files&lt;/h2&gt;
&lt;p&gt;Since updating our SSH keys can be done with user privileges, we will make a user
unit file.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;mkdir -p ~/.config/systemd/user&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;touch ~/.config/systemd/user/ssh-key-update.service&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, paste in the following with your editor of choice:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[Unit]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Description=Update SSH keys from GitHub&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[Service]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ExecStart=/path/to/update-ssh.sh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, we need to make a &lt;code&gt;timer&lt;/code&gt; that will run our &lt;code&gt;ssh-key-update&lt;/code&gt; &lt;code&gt;systemd&lt;/code&gt; service.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;touch ~/.config/systemd/user/ssh-key-update.timer&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Paste in the following:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[Unit]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Description=Update SSH keys from GitHub&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[Timer]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;OnBootSec=5min&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;OnUnitActiveSec=5min&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Unit=ssh-key-update.service&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[Install]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;WantedBy=timers.target&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;enable-and-start-the-timer&quot;&gt;Enable and start the timer&lt;/h2&gt;
&lt;p&gt;Run:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;systemctl --user enable --now ssh-key-update.timer&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You will see that your SSH keys will periodically be fetched and saved like before.
However, when you log out, updates will stop.&lt;/p&gt;
&lt;h2 id=&quot;enable-lingering&quot;&gt;Enable lingering&lt;/h2&gt;
&lt;p&gt;Run:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;loginctl enable-linger&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will enable lingering, which will allow &lt;code&gt;systemd.timer&lt;/code&gt;s to continue running
even if you are not signed in.&lt;/p&gt;</content:encoded></item><item><title>Boingo Hotspot Captive Portal shenanigans</title><link>https://ericswpark.com/blog/2025/2025-12-21-boingo-hotspot-captive-portal/</link><guid isPermaLink="true">https://ericswpark.com/blog/2025/2025-12-21-boingo-hotspot-captive-portal/</guid><pubDate>Sun, 21 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Here are two issues I encountered with the “Boingo Hotspot” Wi-Fi network offered
at some US airports. I’m writing it down here because this was an issue last year,
and I’m sure will continue to be a problem in 2026 when I come back unless they
actually fix their broken captive portal implementation.&lt;/p&gt;
&lt;h2 id=&quot;issue-1--dns-over-tlshttps-doesnt-work&quot;&gt;Issue #1 — DNS-over-TLS/HTTPS doesn’t work&lt;/h2&gt;
&lt;p&gt;I’m using DNS-over-TLS with &lt;code&gt;systemd-resolved&lt;/code&gt;, and when I connected to the free
network in O’Hare, I noticed that DNS resolution requests were timing out with
&lt;code&gt;dig&lt;/code&gt;. The issue? Boingo Hotspot blocks DNS-over-TLS/HTTPS and only allows querying
through the standard DNS port of &lt;code&gt;53&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So, if your &lt;code&gt;/etc/systemd/resolved.conf&lt;/code&gt; (or &lt;code&gt;/etc/systemd/resolved.conf.d/dns-over-tls.conf&lt;/code&gt;)
looks something like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[Resolve]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;DNS=1.1.1.1#one.one.one.one&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;DNSOverTLS=yes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Domains=~.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then you’ll get the following message when you try to &lt;code&gt;dig&lt;/code&gt; for domains:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$ dig google.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;;; communications error to 127.0.0.53#53: timed out&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;;; communications error to 127.0.0.53#53: timed out&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The fix? Disable DNS-over-TLS. You can modify the configuration file,
but that disables DNS-over-TLS for all connections. Instead, blacklist the
specific Boingo Hotspot SSID through (assuming you are using):&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$ nmcli connection show&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$ nmcli connection modify _Free_ORD_Wi-Fi connection.dns-over-tls 0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;0&lt;/code&gt; means disable, &lt;code&gt;1&lt;/code&gt; means opportunistically enable, and &lt;code&gt;2&lt;/code&gt; means enable.&lt;/p&gt;
&lt;p&gt;Now, you can verify that DNS-over-TLS was disabled by running &lt;code&gt;systemd-resolve --status&lt;/code&gt;,
which should result in something like the following output:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$ systemd-resolve --status&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Global&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;           Protocols: +LLMNR +mDNS +DNSOverTLS DNSSEC=yes/unsupported&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Link 3 (wlan0)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    Current Scopes: DNS LLMNR/IPv4 LLMNR/IPv6 mDNS/IPv4 mDNS/IPv6&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;         Protocols: +DefaultRoute +LLMNR +mDNS -DNSOverTLS DNSSEC=yes/supported&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Current DNS Server: 100.109.0.31&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;       DNS Servers: 100.109.0.32 100.109.0.31&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     Default Route: yes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice the &lt;code&gt;-&lt;/code&gt; in front of &lt;code&gt;DNSOverTLS&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;With that, I fixed DNS resolutions, but I still couldn’t get any Internet access.
And that’s because&lt;/p&gt;
&lt;h2 id=&quot;issue-2--dns-hijacking-doesnt-work-with-https&quot;&gt;Issue #2 — DNS hijacking doesn’t work with HTTPS&lt;/h2&gt;
&lt;p&gt;This is more of a general tip, as I’m not aware of any captive portal implementation
that can DNS hijack something like &lt;a href=&quot;https://google.com&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;google.com&lt;/a&gt;. To work around this
problem, most OSes and browser companies use a dedicated HTTP URL for checking
connectivity, but this can sometimes fail. And with HSTS becoming more and more
prevalent, it’s hard to find a website that will let you downgrade to HTTP.&lt;/p&gt;
&lt;p&gt;The solution is to visit &lt;a href=&quot;https://neverssl.com&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;neverssl.com&lt;/a&gt;, which will hopefully
redirect you to the captive portal.&lt;/p&gt;
&lt;p&gt;I still don’t understand why businesses insist on using captive portals. Sure,
you get the ad revenue, but this is such a terrible user experience.&lt;/p&gt;</content:encoded></item><item><title>When your controller doesn&apos;t work, blame your keyboard</title><link>https://ericswpark.com/blog/2025/2025-12-11-linux-controller/</link><guid isPermaLink="true">https://ericswpark.com/blog/2025/2025-12-11-linux-controller/</guid><pubDate>Thu, 11 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I think it should be illegal for game companies and indie studios to drop so
many nice games (and DLCs to games that I loved) during finals season, but my Linux
machine tried to save my grades one last time by not letting me play with my controller.&lt;/p&gt;
&lt;p&gt;More specifically, my Xbox One controller would pair fine with my Linux laptop,
and Steam would detect it and launch Big Picture Mode when I hit the Xbox button.
However, once I was actually in game, the controller wouldn’t work.&lt;/p&gt;
&lt;p&gt;I tried enabling Steam Input, to see if having it go through Steam’s controller
translation layer would maybe make the game detect it, but it wasn’t working.
I wondered how controllers were represented in the Linux kernel and poked around
in &lt;code&gt;/dev/input&lt;/code&gt;, and sure enough, looking through that directory yielded me &lt;code&gt;js0&lt;/code&gt;.
Short for joystick-0, apparently.&lt;/p&gt;
&lt;p&gt;So if I &lt;code&gt;cat /dev/input/js0&lt;/code&gt;, I should see a bunch of gibberish printed to the
console whenever I interacted with my controller, right? But I got absolutely
nothing.&lt;/p&gt;
&lt;p&gt;And then I noticed &lt;code&gt;js1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/dev/input&lt;/code&gt;, like other device folders, has a neat little &lt;code&gt;by-id&lt;/code&gt; folder inside
that maps devices via their identifier and not the order in which they connected
to the system. And sure enough:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$ ls /dev/input/by-id/*joystick&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;/dev/input/by-id/usb-Keychron_Keychron_K2_Pro-if02-event-joystick&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I unplugged my external keyboard from my laptop and restarted the game. And sure
enough, my game started to pick up my controller input again.&lt;/p&gt;
&lt;p&gt;Now to keep my self-preservation instincts until next Friday.&lt;/p&gt;</content:encoded></item><item><title>Why I&apos;m moving away from NixOS</title><link>https://ericswpark.com/blog/2025/2025-09-20-why-im-moving-away-from-nixos/</link><guid isPermaLink="true">https://ericswpark.com/blog/2025/2025-09-20-why-im-moving-away-from-nixos/</guid><pubDate>Sat, 20 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;For nearly one year, I’ve been trying out NixOS, meticulously writing out
&lt;a href=&quot;https://github.com/ericswpark/nixos-config&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;the configuration for my machines in my repository&lt;/a&gt;.
Every new program install, every configuration change, for all of my devices
running NixOS was carefully packaged up into a commit and put into the repository.&lt;/p&gt;
&lt;p&gt;Now, after nearly a year of experimentation, here are the pros and cons that I’ve
learned, and why I’m ultimately switching away from NixOS.&lt;/p&gt;
&lt;h2 id=&quot;disclaimer&quot;&gt;Disclaimer&lt;/h2&gt;
&lt;p&gt;Usual disclaimer because people love to engage in tribalism.&lt;/p&gt;
&lt;p&gt;This blog post details why &lt;em&gt;I&lt;/em&gt; switched away from NixOS. Some of these pros
and cons may not apply to you. Some of them you may find an easy workaround for.
Some of them you may find baseless.&lt;/p&gt;
&lt;p&gt;Please write about them in the comments in the hopes that it may help others
(and if it addresses some of the points in this blog post I may edit it to reference
your comment), but please refrain from pushing &lt;em&gt;your&lt;/em&gt; viewpoint as the ultimate
solution, because it probably isn’t for &lt;em&gt;absolutely everyone&lt;/em&gt;. Thanks.&lt;/p&gt;
&lt;h2 id=&quot;pros&quot;&gt;Pros&lt;/h2&gt;
&lt;p&gt;I just want to get the pros out of the way, because there were certain niceties
that I came to enjoy using NixOS!&lt;/p&gt;
&lt;h3 id=&quot;fast-recovery-time-from-scratch&quot;&gt;Fast recovery time from scratch&lt;/h3&gt;
&lt;p&gt;If my laptop breaks, my SSD dies, or I make a horrible mistake and ruin my
workspace, the traditional way of a re-install would dictate I make an install
media, wipe my drive, re-install the OS, install any drivers (yes, even for
macOS, they don’t ship with literally everything), install all my programs, then
set them up one by one.&lt;/p&gt;
&lt;p&gt;With NixOS, this is also mostly true, except for the parts after the OS install.
Instead, it is cloning my repository, making a symlink, and running
&lt;code&gt;sudo nixos-rebuild switch&lt;/code&gt;. And I get mostly&lt;sup&gt;&lt;a href=&quot;#user-content-fn-1&quot; id=&quot;user-content-fnref-1&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; my computer back, with all the
programs I installed!&lt;/p&gt;
&lt;p&gt;Sure, if the specific programs do not support being configured by NixOS, then
I have to set them up, but overall, this drastically reduces the time I have to
spend remembering and re-installing all of my programs that I used to have.&lt;/p&gt;
&lt;h3 id=&quot;fast-recovery-time-from-bad-configurations&quot;&gt;Fast recovery time from bad configurations&lt;/h3&gt;
&lt;p&gt;If I make a mistake configuring something, or if I install a program that I do
not want, recovery is literally a &lt;code&gt;git revert&lt;/code&gt; and a &lt;code&gt;sudo nixos-rebuild switch&lt;/code&gt;
away.&lt;/p&gt;
&lt;p&gt;I can even tell what broke my computer with a &lt;code&gt;git bisect&lt;/code&gt;. Imagine bisecting
all the decisions you made configuring your computer to figure out where it
all went wrong. Well, with NixOS, you can!&lt;/p&gt;
&lt;p&gt;And because each configuration change can be documented with a Git commit message,
you don’t have to scratch your head in six months and wonder why the hell you
set things up in a certain way.&lt;/p&gt;
&lt;h2 id=&quot;cons&quot;&gt;Cons&lt;/h2&gt;
&lt;p&gt;Only two pros and straight into cons? (Yeah, I mean, look at the title of this
blog post…)&lt;/p&gt;
&lt;h3 id=&quot;the-friction-of-installing-things&quot;&gt;The friction of installing things&lt;/h3&gt;
&lt;p&gt;On other operating systems, installing an app is double-clicking an &lt;code&gt;.exe&lt;/code&gt; and
going through prompts, or dragging an app to a folder from a &lt;code&gt;.dmg&lt;/code&gt;, or typing
&lt;code&gt;sudo pacman -S blender&lt;/code&gt;. On NixOS though, every program install requires you to
open up your configuration repository, locate the Nix configuration file
corresponding to the host that you want to configure, and add up a new line for
the program you want to install.&lt;/p&gt;
&lt;p&gt;Actually. The full process is going to &lt;a href=&quot;https://search.nixos.org&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;search.nixos.org&lt;/a&gt;,
finding the package name of the package you want to install, opening up your
configuration repository, locating the Nix configuration file corresponding to
the host that you want to configure, and adding up a new line for the program
you want to install.&lt;/p&gt;
&lt;p&gt;(I know some NixOS experts will say there are commands like &lt;code&gt;nix-locate&lt;/code&gt;, but
with my usage of them the past couple of months they aren’t perfect. For example,
finding the &lt;code&gt;dig&lt;/code&gt; tool inside the &lt;code&gt;dnsutils&lt;/code&gt; package was not something I managed
to achieve with &lt;code&gt;nix-locate&lt;/code&gt; but the actual website surfaced it.)&lt;/p&gt;
&lt;p&gt;And those are for programs that are tracked by &lt;code&gt;nixpkgs&lt;/code&gt;. So what if it isn’t?&lt;/p&gt;
&lt;h3 id=&quot;nixos-requires-you-to-know-or-learn-nix&quot;&gt;NixOS requires you to know or learn Nix&lt;/h3&gt;
&lt;p&gt;“I’m using NixOS and I didn’t have to learn Nix.”&lt;/p&gt;
&lt;p&gt;Maybe, but when you run into this issue, you’ll probably wish you had to.&lt;/p&gt;
&lt;p&gt;As long as you stick to the packages available in &lt;code&gt;nixpkgs&lt;/code&gt;, you should be mostly
okay with your daily usage of NixOS. But run into a package that &lt;em&gt;isn’t&lt;/em&gt; available,
and you’re suddenly in for a world of hurt.&lt;/p&gt;
&lt;p&gt;In my case, &lt;code&gt;nimf&lt;/code&gt;, the Korean input method editor that I love on Linux, and
&lt;code&gt;python-validity&lt;/code&gt;, the software package that powers the fingerprint reader on my
ThinkPad T480, were all unavailable on &lt;code&gt;nixpkgs&lt;/code&gt;. They used to be open as
packaging requests on &lt;code&gt;nixpkgs&lt;/code&gt; (the latter of which I submitted myself):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/NixOS/nixpkgs/issues/207116&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://github.com/NixOS/nixpkgs/issues/207116&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/NixOS/nixpkgs/issues/357359&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://github.com/NixOS/nixpkgs/issues/357359&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;but were all closed when NixOS maintainers decided to stop taking any packaging
requests&lt;sup&gt;&lt;a href=&quot;#user-content-fn-2&quot; id=&quot;user-content-fnref-2&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;. Now, if you want them, &lt;strong&gt;you should contribute them yourself&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Message from the auto-close bot&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;939&quot; height=&quot;319&quot; src=&quot;/_astro/nixpkgs-package-yourself.qeEtXozg_IqTpd.webp&quot; &gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you wish to see this package in Nixpkgs, &lt;strong&gt;we encourage you to contribute
it yourself&lt;/strong&gt;, via a Pull Request.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So. If you don’t know Nix, you can’t do those things, because everything in
NixOS runs atop Nix, so you better learn it or spend time learning it instead
of using the tools that you want to use.&lt;/p&gt;
&lt;p&gt;“Isn’t this true for all distributions?” You say. Sure. With Arch, you probably
have to write your own &lt;code&gt;PKGBUILD&lt;/code&gt; manifest to package your own program. But that
doesn’t require you to learn a whole new language. (The example I brought up,
&lt;code&gt;PKGBUILD&lt;/code&gt;, only means you need to know &lt;code&gt;bash&lt;/code&gt;, and
&lt;a href=&quot;https://wiki.archlinux.org/title/PKGBUILD&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;the details are nicely documented in the Arch wiki&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;And suppose you don’t want to learn how to use Arch’s &lt;code&gt;PKGBUILD&lt;/code&gt;. Fine. Then
Arch has another escape hatch up its sleeve. You can use &lt;code&gt;PKGBUILD&lt;/code&gt;s provided by
the community through the AUR, or Arch User Repository.&lt;/p&gt;
&lt;p&gt;“Okay, but the AUR can have malicious &lt;code&gt;PKGBUILD&lt;/code&gt;s.” Fine. (We’ll overlook the fact
that you fully trust &lt;code&gt;nixpkgs&lt;/code&gt; and all of its contributors.) There’s yet another
escape hatch offered by other distributions. You can just compile and install
the package yourself, no packaging tool config needed. You can’t do that on NixOS,
because the system directories are immutable and you can’t modify them. Any
forced changes will be gone by the next generation build.&lt;/p&gt;
&lt;p&gt;I’m not saying that NixOS shouldn’t have been built on top of Nix. That wouldn’t
make sense. They literally have it in the distribution name. But through this
approach, it makes it tricky for newcomers to quickly adapt and install things
that they need if they do not know the core language powering the OS.&lt;/p&gt;
&lt;p&gt;“Well, duh, of course you need to learn Nix to work NixOS. It’s like going to
Japan and expecting people to speak English.” Sure, but there are workarounds
to living in Japan without learning Japanese. There are workarounds on other
distributions if you don’t really know their &lt;code&gt;PKGBUILD&lt;/code&gt; system and just want a
tool to work. But with NixOS and Nix, immutability comes into play, restricting
a lot of the escape hatches that would’ve alleviated some of those problems.
Some might see it was a bonus, a feature, but I think you’ll agree that it is
at least painful if you do not know these things.&lt;/p&gt;
&lt;h3 id=&quot;the-friction-of-upgrading-things&quot;&gt;The friction of upgrading things&lt;/h3&gt;
&lt;p&gt;Say you receive a file that requires version 3.6.12, but the installed version on
your computer is 3.6.6. How do you proceed?&lt;/p&gt;
&lt;p&gt;On other OSes, you just grab the installer for 3.6.12, install, and be on your
way. Or it’s a &lt;code&gt;sudo pacman -S blender&lt;/code&gt; or &lt;code&gt;sudo apt upgrade blender&lt;/code&gt;, or whatever.
The point is, you can upgrade just a single package on other operating systems.&lt;/p&gt;
&lt;p&gt;On NixOS, from what I can tell, this is not possible. All the package metadata
goes into the &lt;a href=&quot;https://github.com/NixOS/nixpkgs/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;nixpkgs&lt;/code&gt; repository&lt;/a&gt;, and each version bump is tracked
as a Git commit. While this is probably great for the NixOS folks maintaining
everything, this means that all the package versions are stuck at whatever commit
revision is locked inside your Nix “flake” lockfile, until you change that commit
revision to a newer one that includes version 3.6.12.&lt;/p&gt;
&lt;p&gt;The trouble is, any commit between your initial commit revision and the new
3.6.12 commit revision also gets included, which includes other package upgrades.
Thus, what was a simple single package upgrade now turns into a 20+ min (or even
a few hours if you didn’t upgrade for a while!) hassle.&lt;/p&gt;
&lt;p&gt;Also, after you’ve finished the upgrading process, there’s no easy way to know
exactly which versions have changed between the last &lt;code&gt;flake&lt;/code&gt; lock and the current
one. I only realize that KDE must’ve gotten a new major version bump when I reboot
my laptop and the wallpaper changes, or when I open up a program and get a “What’s
New” popup. NixOS doesn’t really show you a list of packages that will be upgraded.&lt;/p&gt;
&lt;h3 id=&quot;the-speed-of-compiling-things&quot;&gt;The speed of compiling things&lt;/h3&gt;
&lt;p&gt;So why does upgrading take so long?&lt;/p&gt;
&lt;p&gt;Usually, when I take a peek at my console while it is upgrading my system, I see
a lot of packages being compiled into binary form that can then be installed on
my system.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;But NixOS has a binary cache, available at &lt;a href=&quot;https://cache.nixos.org&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;cache.nixos.org&lt;/a&gt;! Your
builds shouldn’t be taking that long! What gives?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Well, as the name of the subdomain implies, packages first have to be built on
NixOS’s servers before they are “cached” for distribution. If the requested
package has never been requested before, or if there has been a new update to an
existing package, a build job has to run before the cached binaries are available
for download.&lt;/p&gt;
&lt;p&gt;And if there are tweaks to the build process in your configuration, that requires
another binary, which is probably not cached by NixOS’s servers. Which means NixOS
immediately defaults to compiling the package from source on your system.&lt;/p&gt;
&lt;p&gt;You can set up build cache servers yourself, and this means that you don’t have
to turn your weak laptop into a furnace every time you run a &lt;code&gt;switch&lt;/code&gt;&lt;sup&gt;&lt;a href=&quot;#user-content-fn-3&quot; id=&quot;user-content-fnref-3&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;, but it’s
yet another thing that you have to configure and keep track of.&lt;/p&gt;
&lt;h3 id=&quot;configuring-everything-through-nixos&quot;&gt;Configuring “everything” through NixOS&lt;/h3&gt;
&lt;p&gt;The beauty of documenting configuration in your NixOS configs is great, up to a
point. And that point is when you want to configure something that hasn’t been
translated to a Nix configuration line yet.&lt;/p&gt;
&lt;p&gt;Think of some hidden checkbox in that productivity suite that you use that isn’t
recorded as a config file change anywhere. Think of some cloud-only app that
saves host configuration using your hostname. Okay, perhaps those are contrived
examples, but the fact is that not absolutely “everything” can be configured through
NixOS.&lt;/p&gt;
&lt;p&gt;And sometimes, that’s okay. Like for example, the desktop environment I use — KDE
— has a bunch of options that I like to tweak for each system. But if I had to
make a change in my configuration file each time I tweaked an option or messed
with a checkbox, just to make sure the setting would persist if I ever reinstalled
my system, I think I would go mad.&lt;/p&gt;
&lt;p&gt;Of course, there are tools like &lt;code&gt;rc2nix&lt;/code&gt; in the KDE instance that offers to convert
your current configuration into a Nix config. But in my usage, this hasn’t been
perfect, since the KDE suite of apps is inconsistent and store configuration files
in all sorts of places. (I mean, just look at Dolphin with its &lt;code&gt;.dolphinrc&lt;/code&gt;.
&lt;code&gt;rc2nix&lt;/code&gt; can’t capture that. Then you have to figure out where it is and what
config lines are pertinent to your specific host and then make a Nix config
for that in your config repository and make sure the symlink works and then make
sure new lines appended by Dolphin as you use it don’t get wiped out during
a rebuild and re-symlink and so on and so forth, and you can see why it quickly
becomes a massive headache.)&lt;/p&gt;
&lt;p&gt;Like I mentioned with Dolphin, not all programs are friendly to being configured
externally. Programs are meant to run anywhere, on most major operating systems,
and on most major distributions if packaged for Linux. It’s very highly probable
that the programs and tools you use do not know the existence of NixOS and are
not written to be accommodating towards it.&lt;/p&gt;
&lt;p&gt;And if a configuration change requires you to copy over or symlink a configuration
file, any change you make within the program gets wiped out when NixOS rebuilds
a generation and redos the configuration step, which is not a good user experience.
You’re meant to configure “everything” (possible) through NixOS, but as we’ve
established, you can’t configure “everything” through NixOS, so you end up with
a bad time when the two configuration sources clash and overwrite each other.&lt;/p&gt;
&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;So does that mean NixOS is bad and you shouldn’t use it? No. I still enjoyed my
time using and learning NixOS. But I have a certain way of using my systems, and
over time I’ve realized that my way of computing does not really match the
declarative, immutable approach that NixOS takes.&lt;/p&gt;
&lt;p&gt;Perhaps I’ll give NixOS another try once I have the time available to thoroughly
learn Nix, and once the documentation for NixOS has matured to a point where it
is easy to address some of the pain points I experienced above. But for now, I
really just want to use my computer to do the things I want to do, and NixOS has
become more of a hindrance with its cons than the benefits it provides through
its features.&lt;/p&gt;
&lt;hr&gt;
&lt;section data-footnotes=&quot;&quot; class=&quot;footnotes&quot;&gt;&lt;h2 class=&quot;sr-only&quot; id=&quot;footnote-label&quot;&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li id=&quot;user-content-fn-1&quot;&gt;
&lt;p&gt;I say mostly here because of the con I’ll get to in a second. &lt;a href=&quot;#user-content-fnref-1&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 1&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-2&quot;&gt;
&lt;p&gt;Which is totally valid, it is their right to do so! It does make it harder
for users, so my point still stands. &lt;a href=&quot;#user-content-fnref-2&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 2&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-3&quot;&gt;
&lt;p&gt;This is a really handy alias, and I’m not sure why it’s not the default.
&lt;a href=&quot;https://github.com/ericswpark/nixos-config/blob/master/home-manager/common/nixos-aliases.nix&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;But you can configure it yourself if you want.&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-3&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 3&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</content:encoded></item><item><title>Bits and pieces from my Bluu internship</title><link>https://ericswpark.com/blog/2025/2025-08-29-bits-and-pieces-from-my-bluu-internship/</link><guid isPermaLink="true">https://ericswpark.com/blog/2025/2025-08-29-bits-and-pieces-from-my-bluu-internship/</guid><pubDate>Fri, 29 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I’ve been meaning to write about what I worked on during my internship, but I
didn’t get a chance to do so until today. So here are the two artifacts that I
worked on during Summer 2025, which I have been allowed to share as I worked on
them during my spare time:&lt;/p&gt;
&lt;h1 id=&quot;the-report-tool&quot;&gt;The report tool&lt;/h1&gt;
&lt;p&gt;Many companies have a time-tracking system where they track what their employees
have done over a given day. We were supposed to write up our individual reports,
collate them, and then send them off as one giant team report. The trouble was,
collating them into one nice table was rather cumbersome and annoying,
especially if changes had to be made last-minute as it would shift the table
layout.&lt;/p&gt;
&lt;p&gt;So I made a web app that would allow users to type in their work item, export
their daily report, and collate them in one go into one table. It even has some
nice quality-of-life features, like automatic sorting of the name and projects
based on hierarchy and priority. (Plus, it has a hidden Easter egg!)&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://ericswpark.com/bluu_daily-event-report-tool/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Check out the web app here&lt;/a&gt; (warning: is in Korean). And the
&lt;a href=&quot;https://github.com/ericswpark/bluu_daily-event-report-tool&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;source code is available here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;One tidbit if you do decide to look at the source code. I noted this in the
&lt;code&gt;README.md&lt;/code&gt;, but the entire page is just one HTML file with inline Babel and
&lt;code&gt;&amp;#x3C;script&gt;&lt;/code&gt; tags powering everything. The reason was because at the time, I did
not have any development tools installed on my work computer, and I didn’t want
to work on this on my personal computer and then transfer it over — that would’ve
been a whole headache on its own. So everything is in one file, but thanks to
modern text editors/IDEs like VS Code (which was thankfully available on the
Microsoft Store), it wasn’t that hard to edit.&lt;/p&gt;
&lt;h1 id=&quot;the-automatic-time-card-script&quot;&gt;The automatic time-card script&lt;/h1&gt;
&lt;p&gt;ADP is a payroll/HR platform that companies use to keep track of employee time
cards. One fatal flaw of ADP (or perhaps the browser?) is that on Microsoft Edge,
the password autofill doesn’t work. Which meant every morning, I had to run up
to my desk, wait for Windows to wake up, wait for the ADP page to load, type in
my credentials like a savage, wait for the main page to load, and finally click
on the clock-in button.&lt;/p&gt;
&lt;p&gt;So instead of doing the normal thing of switching to a different Chromium-based
browser, I wrote up a little Python script that will do the ADP clock-in for me.
Now, I just have to click on a shortcut and go make coffee, and by the time I get
back I’ll have been clocked in.&lt;/p&gt;
&lt;p&gt;Disclaimer 1: this script can and certainly will break if ADP updates their
website design.&lt;/p&gt;
&lt;p&gt;Disclaimer 2: this script can be a little flaky at times depending on the timing,
so make sure you are actually clocked-in after your coffee break!&lt;/p&gt;
&lt;p&gt;Disclaimer 3: manually running the script once you come into work is fine, but
if you put it on some scheduler and automate it while you haven’t come into work,
that can be illegal. I’m not a lawyer.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/ericswpark/d405fa6fe50885e7b2bb3b2360542316&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Here is a Gist to the entire script&lt;/a&gt;. You will have to supply
your own credentials and perhaps change the WebDriver path for your specific
browser.&lt;/p&gt;
&lt;p&gt;That wraps it up for this blog post! Those were the small things I got to work
on this summer that I was allowed to share.&lt;/p&gt;</content:encoded></item><item><title>A rant about cheap proprietary IP cameras (featuring Hej Home)</title><link>https://ericswpark.com/blog/2025/2025-04-18-a-rant-about-cheap-proprietary-ip-cameras-featuring-hej-home/</link><guid isPermaLink="true">https://ericswpark.com/blog/2025/2025-04-18-a-rant-about-cheap-proprietary-ip-cameras-featuring-hej-home/</guid><pubDate>Fri, 18 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A couple of months back, I was helping with cleaning out a relative’s closet when we came across this cheap-looking indoor IP camera from a company named Hej Home (헤이홈). The relative didn’t need it or want it, so I thought it might be fun to figure out how it ticks, and potentially have it set up as a local-only IP camera.&lt;/p&gt;
&lt;p&gt;Before I get your hopes up too much, I didn’t really succeed in the latter half. This blog post documents my attempted journey on getting this camera to work for me, and a rant on all this garbage, crap IoT devices with no local access options, contributing to the e-waste epidemic everywhere.&lt;/p&gt;
&lt;h1 id=&quot;specifications&quot;&gt;Specifications&lt;/h1&gt;
&lt;p&gt;The unit I have is the “Smart Home Camera Pro (스마트 홈카메라 프로)”, model code &lt;code&gt;GKW-MC055&lt;/code&gt;. It has one micro-USB port that carries over the power pins only, a reset hole, and a microSD card slot.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Main body of the camera&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;2020&quot; height=&quot;3290&quot; src=&quot;/_astro/main-body.DOFltYBa_esVsb.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;There is one tiny lens in front, with two LED indicators. One of the LED indicators is used for the status of the camera and lights up red when power is initially connected. Once the camera has finished initializing, the LED turns blue. The other indicator light denotes whether the camera is in night-vision mode. In the night-vision mode, an array of IR LEDs on the perimeter ring of the lens turn on to illuminate the area.&lt;/p&gt;
&lt;p&gt;The camera also has a speaker and a mic for bidirectional communication via the app.&lt;/p&gt;
&lt;p&gt;Speaking of the app, let’s try setting it up!&lt;/p&gt;
&lt;h1 id=&quot;setting-up-the-camera-on-the-official-app&quot;&gt;Setting up the camera on the official app&lt;/h1&gt;
&lt;p&gt;As always, the best place to start is the official manufacturer app, so I downloaded it and it asked me to set up an account. &lt;em&gt;Fine, I guess&lt;/em&gt;. I set up the account and confirmed that I could actually see myself in the camera stream.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Screenshot of the app&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1440&quot; height=&quot;3120&quot; src=&quot;/_astro/app-screenshot.CT2UGtiN_Z1jiPfb.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;I then trawled through the settings menu, looking for any options to enable local access via RTSP or ONVIF. Unfortunately, no such setting existed. Looking through the FAQ section yielded no results.&lt;/p&gt;
&lt;h1 id=&quot;port-scanning-the-ip-camera&quot;&gt;Port-scanning the IP camera&lt;/h1&gt;
&lt;p&gt;&lt;em&gt;Perhaps the local access ports are already open?&lt;/em&gt; I thought.&lt;/p&gt;
&lt;p&gt;Time to port-scan this camera!&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$ nmap 192.168.8.123&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Starting Nmap 7.95 ( https://nmap.org ) at 2025-04-17 13:33 US Eastern Daylight Time&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Nmap scan report for xxx.lan (192.168.8.123)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Host is up (0.022s latency).&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Not shown: 999 closed tcp ports (reset)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;PORT     STATE SERVICE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;6668/tcp open  irc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;MAC Address: B4:FB:E3:XX:XX:XX (AltoBeam (China))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Nmap done: 1 IP address (1 host up) scanned in 2.28 seconds&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That was strange. There was only one port open, and I was pretty sure the camera didn’t have an IRC server running. I tried a more comprehensive scan, but it yielded the exact same result:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$ nmap -sS -p- 192.168.8.123&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Starting Nmap 7.95 ( https://nmap.org ) at 2025-04-17 13:35 US Eastern Daylight Time&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Nmap scan report for xxx.lan (192.168.8.123)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Host is up (0.019s latency).&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Not shown: 65534 closed tcp ports (reset)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;PORT     STATE SERVICE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;6668/tcp open  irc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;MAC Address: B4:FB:E3:XX:XX:XX (AltoBeam (China))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Nmap done: 1 IP address (1 host up) scanned in 28.72 seconds&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Looking online, port &lt;code&gt;6668&lt;/code&gt; seemed to be used by IoT devices that were based on the Tuya SDK, which was this company that specialized in licensing out its IoT platform for other companies to rebrand and re-sell. Hej Home seemed to be one of their clients, which explained &lt;a href=&quot;https://hej.life/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;why they were able to churn out so many IoT devices on their store&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But that’s not going to help me here. I looked up if there was a way to enable ONVIF with the underlying Tuya SDK and &lt;a href=&quot;https://developer.tuya.com/en/docs/iot-device-dev/tuyaos-package-ipc-device?id=Kcn1qb261j9je&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;found this page&lt;/a&gt;. It seemed like enabling ONVIF was a simple configuration swap, so I sent off an email to the company behind Hej Home and asked if they would please consider enabling this. (It would cost them literally nothing to do this — it’s a config change and as far as I know Tuya won’t charge them extra for it or whatever.) Sadly, I’ve yet to hear back from them after emailing them on March 25.&lt;/p&gt;
&lt;p&gt;So that’s out. What’s next?&lt;/p&gt;
&lt;h1 id=&quot;disassembling-the-camera&quot;&gt;Disassembling the camera&lt;/h1&gt;
&lt;p&gt;I decided to disassemble the camera and perhaps dump the firmware from the flash chip inside to see if I could glean any more clues from it. I made a false start trying to get into the camera from the bottom half — that only yielded access to the microUSB port, which was good if I ever wanted to transplant a USB-C port in there, since I only needed to solder on the power wires. Unfortunately, the brain and major parts of the camera were not inside the lower half like I assumed it would, and if I had been any less gentle with it I probably would’ve broken the camera, ending this exploration right then and there.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Top half disassembly&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1167&quot; height=&quot;982&quot; src=&quot;/_astro/top-half-disassembly.vpd906PD_Z2kgF54.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;I eventually figured out that you had to separate the top half of the camera. More specifically, the globe shape split into two spherical halves, allowing me to extract the motherboard and the lens assembly, which was sandwiched together into one slim package. This small sandwich controlled the entirety of the camera, from the motors used to move it horizontally and vertically to camera control, SD card recording, and power input from the port downstairs.&lt;/p&gt;
&lt;h1 id=&quot;dumping-the-spi-flash-chip&quot;&gt;Dumping the SPI flash chip&lt;/h1&gt;
&lt;p&gt;&lt;img alt=&quot;Flash chip from XMC&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1920&quot; height=&quot;1080&quot; src=&quot;/_astro/flash-chip.CiZdQaFT_Z1Orgfb.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;My attention immediately landed on the single marked flash chip, from XMC. The marking read &lt;code&gt;XMCQH64AHIG&lt;/code&gt;, and I was pretty sure I could use &lt;code&gt;flashrom&lt;/code&gt; to dump its contents. Unfortunately, I stumbled across this &lt;a href=&quot;https://github.com/flashrom/flashrom/issues/148&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;open issue on the flashrom GitHub&lt;/a&gt;, which had been open since July 2020.&lt;/p&gt;
&lt;p&gt;I tried to figure out why the two linked pull requests had been closed, and discovered that the &lt;code&gt;flashrom&lt;/code&gt; team didn’t use GitHub for development. Rather, they had their own Gerrit instance where they reviewed and merged patches. The &lt;a href=&quot;https://github.com/flashrom/flashrom/pull/150&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;associated pull request&lt;/a&gt; (actually, &lt;a href=&quot;https://github.com/flashrom/flashrom/pull/239&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;multiple of them&lt;/a&gt;) had been transferred to Gerrit but had been lost to time, and the original PRs had been closed.&lt;/p&gt;
&lt;p&gt;So I tried &lt;a href=&quot;https://review.coreboot.org/c/flashrom/+/86990&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;my luck by submitting a patch myself&lt;/a&gt;, which actually got merged in March 29! So after 5 years, &lt;code&gt;flashrom&lt;/code&gt; gained the ability to read and write from this chip.&lt;/p&gt;
&lt;h1 id=&quot;looking-at-the-firmware-dump&quot;&gt;Looking at the firmware dump&lt;/h1&gt;
&lt;p&gt;&lt;img alt=&quot;Flash chip reading&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;3000&quot; height=&quot;4000&quot; src=&quot;/_astro/flash-chip-reading.BWVa_fSO_ZBwHnn.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;Now that the chip definition had been added, I used the &lt;code&gt;flashrom&lt;/code&gt; development binary I compiled to rip the firmware from the board. I then used &lt;code&gt;binwalk -Me cctv.bin&lt;/code&gt; to extract the contents and had a look around.&lt;/p&gt;
&lt;p&gt;There was a file named &lt;code&gt;tuya_config.json&lt;/code&gt; in one of the extracted folders that looked very promising. One of the config options caught my eye:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;onvif_enable&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;What would happen if I just changed that to &lt;code&gt;1&lt;/code&gt;?&lt;/em&gt; I thought. So I fired up &lt;code&gt;hexedit&lt;/code&gt; on a copy of the firmware dump, searched manually for &lt;code&gt;6F 6E 76 69 66 5F 65 6E 61 62 6C 65&lt;/code&gt; (which was &lt;code&gt;onvif_enable&lt;/code&gt; in hexadecimal), and replaced the value &lt;code&gt;30&lt;/code&gt; (&lt;code&gt;0&lt;/code&gt;) to &lt;code&gt;31&lt;/code&gt; (&lt;code&gt;1&lt;/code&gt;). I then flashed the firmware back onto the camera and connected it to power.&lt;/p&gt;
&lt;p&gt;During startup, a startling chime came from the camera which I had never heard before. I got nervous and excited for a moment, thinking that ONVIF had finally been enabled. Unfortunately, &lt;code&gt;nmap&lt;/code&gt; showed that there were no new open ports on the camera.&lt;/p&gt;
&lt;p&gt;I then looked into whether I could flash a completely new firmware image, overwriting whatever proprietary crap that Hej Home and Tuya had on there.&lt;/p&gt;
&lt;h1 id=&quot;looking-for-open-source-options&quot;&gt;Looking for open-source options&lt;/h1&gt;
&lt;p&gt;&lt;img alt=&quot;Anyka AK3918 SoC&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;3804&quot; height=&quot;3416&quot; src=&quot;/_astro/anyka-soc.mUHiuE9O_1jk3zO.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;From my previous expedition, I knew that the SoC of the camera was this chip marked &lt;code&gt;AK3918&lt;/code&gt; from this company named “Anyka”. Turns out, this was some Chinese company producing an all-in-one SoC for IoT camera makers.&lt;/p&gt;
&lt;p&gt;While there was a datasheet available, the drivers and hardware definition had never been mainlined into the Linux kernel, probably because no Chinese company has the time to do that. Actually, they didn’t even release the kernel sources despite shipping a variant of the Linux kernel, but we all know that GPL is merely a suggestion to these hardware OEMs that they never take seriously.&lt;/p&gt;
&lt;p&gt;There were some repositories attempting to reverse engineer cameras built upon this SoC, but I quickly realized that there were different firmware revisions to this SoC as well, and most of the repos that I found targeted the &lt;code&gt;AK3918v200&lt;/code&gt; revision, while my camera had the &lt;code&gt;AK3918v300&lt;/code&gt; one. Even if I could somehow tweak things to get the &lt;code&gt;v200&lt;/code&gt; repos working on the &lt;code&gt;v300&lt;/code&gt; one, the repositories only had basic scripts available, and it was not clear whether I would be able to even set up RTSP or ONVIF with those open source options.&lt;/p&gt;
&lt;h1 id=&quot;cutting-my-losses-and-moving-on&quot;&gt;Cutting my losses and moving on&lt;/h1&gt;
&lt;p&gt;Unfortunately, this is where I choose to stop. It’s been great getting my hands dirty disassembling this camera, dumping firmware, and contributing to the &lt;code&gt;flashrom&lt;/code&gt; project, but going any further is just not worth my time.&lt;/p&gt;
&lt;p&gt;For reference, &lt;a href=&quot;https://www.amazon.com/Tapo-cameras-for-home-security/dp/B0CH45HPZT&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;a camera on Amazon that supports ONVIF is only around $25&lt;/a&gt;. I can buy it, hook it up, and connect it to Home Assistant with much less hassle than what I just went through. I don’t know how many hours, days, or even weeks it’ll take before I get some semblance of local access working on the original godforsaken camera, but I do know for sure that it’ll be worth much more than $25.&lt;/p&gt;
&lt;p&gt;But forget about this alternative for a second. It’s ridiculous that a camera that is capable of being streamed to a local server without Internet access just can’t do that, because of some configuration change that’s locked away in a proprietary SDK. Without it, this camera becomes worthless to me, and probably to a vast majority of others as well. Nobody would be comfortable streaming the inside of their home to some server farm in China somewhere… or at least nobody &lt;em&gt;should&lt;/em&gt; be comfortable in doing so.&lt;/p&gt;
&lt;p&gt;And yet Tuya and all the client companies that depend on Tuya use this approach because it’s so easy to set up. They can pump out all this e-waste IoT garbage and get the tech illiterate audience to buy them and set them up, since they value the ease of use in not having to deal with setting up port forwarding and a NVR or whatever. But in doing so, they don’t realize that they’re giving a 3rd-party access to their living room space, or their baby crib or whatever.&lt;/p&gt;
&lt;p&gt;So please, if someone you know is thinking about setting up cameras in their home (or any IoT device, really), always stress the importance of local-only access and why having devices phone home via the Internet is not a good idea, even if it is convenient. Home Assistant is extremely easy to set up, and now that they sell ready-made kits to set it up in minutes, there’s no real excuse in not using such a solution.&lt;/p&gt;</content:encoded></item><item><title>Fix up audio delay in Meta Quest recordings</title><link>https://ericswpark.com/blog/2025/2025-04-06-fix-up-audio-delay-in-meta-quest-recordings/</link><guid isPermaLink="true">https://ericswpark.com/blog/2025/2025-04-06-fix-up-audio-delay-in-meta-quest-recordings/</guid><pubDate>Sun, 06 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I bought my Quest 3 in October 2023. Today is April 2025. After 18 months of firmware updates, this bug still lingers: the audio desyncs by a couple hundred milliseconds whenever you use the recording functionality (the “Camera” app) to record gameplay footage inside the Quest.&lt;/p&gt;
&lt;p&gt;I hate having to adjust the delay manually every time I splice the footage in my video editor, so I decided to just automate it with &lt;code&gt;ffmpeg&lt;/code&gt; (all hail &lt;code&gt;ffmpeg&lt;/code&gt;).&lt;/p&gt;
&lt;h1 id=&quot;step-1-figure-out-the-delay-values&quot;&gt;Step 1: Figure out the delay values&lt;/h1&gt;
&lt;p&gt;I first loaded up the video clip in my video player and played around with the audio delay option until I could get the audio and video in sync. A good trick to sync up Beat Saber footage, for example, is to listen for the “click” when you press the play button and the screen turns black, or when your saber goes through a note and makes the piercing sound.&lt;/p&gt;
&lt;p&gt;On MPC-BE, you can find the delay option by opening up the options menu with the ‘O’ key, navigating to “Audio &gt; Sound Processing”, checking “Audio time shift (ms)” and adjusting the delay there. Start with increments of -100 ms. For my headset, I found a delay of -400 ms, although it may be different for you depending on your unit variation and whatever game you’re recording. (Also, I have no idea why this option is buried here…)&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;MPC-BE audio delay options&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1411&quot; height=&quot;1052&quot; src=&quot;/_astro/mpc-be-audio-delay-options.BIsDfe-x_Z1eFKHY.webp&quot; &gt;&lt;/p&gt;
&lt;h1 id=&quot;step-2-ffmpeg&quot;&gt;Step 2: &lt;code&gt;ffmpeg&lt;/code&gt;&lt;/h1&gt;
&lt;p&gt;And the command to shift just the audio with &lt;code&gt;ffmpeg&lt;/code&gt; is the following:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg -i input.mp4 -itsoffset 0.4 -i input.mp4 -map 1:v -map 0:a -c copy output.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Don’t forget to change the offset value if the delay is different for you!&lt;/p&gt;
&lt;h2 id=&quot;i-have-a-positive-delay-value&quot;&gt;I have a positive delay value&lt;/h2&gt;
&lt;p&gt;If your video clip’s audio is ahead of the recording for some reason, use the following:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg -i input.mp4 -itsoffset 0.4 -i input.mp4 -map 0:v -map 1:a -c copy output.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that the &lt;code&gt;-map&lt;/code&gt; numbers have changed, since we are now grabbing the non-delayed video stream from the first input and the delayed audio stream from the second input.&lt;/p&gt;
&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Don’t forget to uncheck the delay in your video player afterwards. (Or else you’ll drive yourself crazy figuring out why all your content has audio sync issues! Not speaking from experience.)&lt;/p&gt;
&lt;p&gt;Also, if the fix is this simple, why can’t the Quest just correct the recording once it is finished…?&lt;/p&gt;</content:encoded></item><item><title>Buying my (Korean) name on the Internet</title><link>https://ericswpark.com/blog/2025/2025-03-30-buying-my-korean-name-on-the-internet/</link><guid isPermaLink="true">https://ericswpark.com/blog/2025/2025-03-30-buying-my-korean-name-on-the-internet/</guid><pubDate>Sun, 30 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;After a intriguing conversation about punycode, on a whim I searched for my Korean name to see if it was available. I was pretty sure it wouldn’t be because it’s a common name.&lt;/p&gt;
&lt;p&gt;Long story short: go see my (Korean) face: &lt;a href=&quot;http://xn--bh3b85gcrf.com&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;http://박선우.com&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>I nearly lost my entire home server</title><link>https://ericswpark.com/blog/2025/2025-03-04-i-nearly-lost-my-entire-server/</link><guid isPermaLink="true">https://ericswpark.com/blog/2025/2025-03-04-i-nearly-lost-my-entire-server/</guid><pubDate>Tue, 04 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Around the first week of January, I said goodbye to my family in Korea and got on a flight back to the US to continue my studies. The flight itself was quite uneventful, and I enjoyed a couple of movies I’d brought on my phone and played games on my Steam Deck. But I don’t like long-haul flights in general because all the shaking around makes it impossible to get any sleep and the noise-canceling headsets can only cut out so much engine roar.&lt;/p&gt;
&lt;p&gt;As the plane touched down and taxied to the gates, I toggled off airplane mode and watched the flurry of notifications and activity as the phone synced with the world after 13 hours of disconnect. I didn’t see anything noteworthy, so I grabbed my bags and got off with the others.&lt;/p&gt;
&lt;p&gt;I went through the visa check, picked up my bags from the carousel, and headed to the multi-modal facility where all the buses were. While waiting for the tram system to come by, Pushover suddenly chimed on my phone. I immediately knew it was not a good sign because that ping sound was configured for critical alerts.&lt;/p&gt;
&lt;p&gt;Sure enough, I was greeted by this, after 13 hours of torture on a flight:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;UnRAID&amp;amp;#x27;s dashboard webUI. Two out of five drives in my array show an X symbol as they are disabled due to errors&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;2432&quot; height=&quot;532&quot; src=&quot;/_astro/initial-array-drive-failures.BMCTeHvL_2oLw2I.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;My first thought was: why did this have to happen &lt;em&gt;after&lt;/em&gt; I’d come all the way to the US?! Answer: Murphy’s Law. Second thought was: thank god for dual-parity. I thought it was overkill when I initially configured my array that way, but now it was evident that I’d missed total catastrophe by a very slim margin.&lt;/p&gt;
&lt;p&gt;I thought it was strange that two drives dropped off of the array at the same time, but didn’t think much of it at the time. (Foreshadowing.) While the drives there were a mix as I bought the drives in batches, there could’ve been a time when I bought two in a single batch. Perhaps it was a manufacturing defect.&lt;/p&gt;
&lt;p&gt;At any rate, I ordered up two 16 TB replacements and took the server offline to prevent any more reads and writes from taxing the other drives and causing them to fail. The server would be unavailable, and my family would be mad at me, but it was better than creating a worse situation where I’d have to reconstruct the server from scratch.&lt;/p&gt;
&lt;p&gt;Now, conforming to best practices, I do have a backup server with a replica of the data on the main server. However, because I wanted to tackle two birds with one stone, the backup server was located offsite, so the schedule for backing up ran every day during the early hours when everyone was asleep, and it was constrained by Internet bandwidth between the two sites. Depending on when the last backup was, it was possible that it didn’t have all the bits of the latest copy on the main server, and at any rate, it was going to be a painful affair trying to restore around 18 TB of data over the Internet.&lt;/p&gt;
&lt;p&gt;So the drives arrived, I got them installed with help from my family, the server resilvered and everything was right with the world, right?&lt;/p&gt;
&lt;h1 id=&quot;an-ideal-world-where-everything-goes-right&quot;&gt;An ideal world where everything goes right&lt;/h1&gt;
&lt;p&gt;When the new hard drives arrived, I got on a video call with my family members and instructed them on how to change the drives. It was slightly tricky because &lt;a href=&quot;/blog/2022/2022-07-04-jonsbo-n1-brief-text-review/&quot;&gt;the case I used for the main server, the Jonsbo N1&lt;/a&gt;, required you to slide the server out of the main case rail and replace the drives. This was a finicky process, because the server was in a media cabinet and that meant you had to take out the entire server and delicately disconnect cables and make sure you didn’t drop it and kill the rest of the drives.&lt;/p&gt;
&lt;p&gt;But we managed to get it done, and I booted the server up and started the rebuild process. I opted to skip the preclearing process, because there was no way the brand-new drives were faulty, right?&lt;/p&gt;
&lt;h1 id=&quot;the-new-drives-are-faulty&quot;&gt;The new drives are… faulty?&lt;/h1&gt;
&lt;p&gt;Unfortunately, the new drives immediately threw up the exact same I/O errors a minute into resilvering. One dropped off from the system entirely, just like the 8 TB one from the two original drives that “failed”. &lt;code&gt;smartctl&lt;/code&gt; couldn’t detect the drive that went AWOL:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Smartctl open device: /dev/sde failed: INQUIRY failed&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;…and gave tons and tons of errors for the other drive, even though all the SMART attributes looked healthy and the drive was deemed okay by SMART standards:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Error 385 occurred at disk power-on lifetime: 20508 hours (854 days + 12 hours)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  When the command that caused the error occurred, the device was active or idle.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  After command completion occurred, registers were:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ER ST SC SN CL CH DH&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -- -- -- -- -- -- --&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  04 61 02 00 00 00 a0  Device Fault; Error: ABRT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Commands leading to the command that caused the error were:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  CR FR SC SN CL CH DH DC   Powered_Up_Time  Command/Feature_Name&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -- -- -- -- -- -- -- --  ----------------  --------------------&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ef 10 02 00 00 00 a0 08      01:08:16.785  SET FEATURES [Enable SATA feature]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ec 00 00 00 00 00 a0 08      01:08:16.785  IDENTIFY DEVICE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ef 03 46 00 00 00 a0 08      01:08:16.785  SET FEATURES [Set transfer mode]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ef 10 02 00 00 00 a0 08      01:08:16.784  SET FEATURES [Enable SATA feature]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ec 00 00 00 00 00 a0 08      01:08:16.783  IDENTIFY DEVICE&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;dmesg&lt;/code&gt; showed more cryptic and sinister errors:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;20:28:04 dipper kernel: ata2.00: configured for UDMA/133 (device error ignored)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;20:28:04 dipper kernel: ata2: EH complete&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;20:28:04 dipper kernel: ata2.00: exception Emask 0x0 SAct 0x0 SErr 0x0 action 0x0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;20:28:04 dipper kernel: ata2.00: irq_stat 0x40000001&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;20:28:04 dipper kernel: ata2.00: failed command: WRITE DMA EXT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;20:28:04 dipper kernel: ata2.00: cmd 35/00:c8:e0:a3:05/00:01:00:00:00/e0 tag 19 dma 233472 out&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;20:28:04 dipper kernel:         res 61/04:c8:e0:a3:05/00:01:00:00:00/e0 Emask 0x1 (device error)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;20:28:04 dipper kernel: ata2.00: status: { DRDY DF ERR }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;20:28:04 dipper kernel: ata2.00: error: { ABRT }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;20:28:04 dipper kernel: ata2.00: failed to enable AA (error_mask=0x1)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So I had to dig deeper. There was no way that the new drives were faulty in the &lt;strong&gt;exact same way&lt;/strong&gt; as the old drives, and both of them at that.&lt;/p&gt;
&lt;h1 id=&quot;going-down-the-chain&quot;&gt;Going down the chain&lt;/h1&gt;
&lt;p&gt;The first thing was to figure out if the drive bays were going bad. I video-called with my (frustrated) brother and had him swap around drives in the bays.&lt;/p&gt;
&lt;p&gt;However, after swapping, the problems followed the drives. This was a real head-scratcher. If the drives weren’t faulty, then something down the chain was the problem. But how come the problems followed the drives across bays if the drives themselves weren’t faulty?&lt;/p&gt;
&lt;p&gt;I asked and checked to make sure that all the connections were firmly seated. This was quite the ordeal, because it’s really hard to tell whether a connection is “good” over a grainy video call. My brother did note that one of the Molex connectors that went to the SATA backplane of the Jonsbo N1 was slightly loose when he went to push it in, so I was hopeful that perhaps it was a power issue. The failure probably followed the drives because they were the most susceptible to brownouts, and the fact that the new drives were now much bigger in capacity than any other drive in the system supported the hypothesis that they were the first ones to quit when the power was insufficient and throw errors.&lt;/p&gt;
&lt;p&gt;Unfortunately, this managed to throw me off the scent temporarily. Even the preclearing process seemed to work fine, so I canceled the preclear and started the rebuild. However, a minute into the rebuild, the same freaking problem returned.&lt;/p&gt;
&lt;p&gt;I had to move further down the chain. My suspects, in order from the drive to my screen thousands of kilometers away, were the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SATA backplane&lt;/li&gt;
&lt;li&gt;The SAS to SATA (1x4) breakout cables (SFF-8087)&lt;/li&gt;
&lt;li&gt;The LSI RAID card, flashed into IT mode, acting as the HBA&lt;/li&gt;
&lt;li&gt;The actual motherboard itself&lt;/li&gt;
&lt;li&gt;The PSU&lt;/li&gt;
&lt;li&gt;Me, I was going insane and this was all a hallucination&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Of particular note was the breakout cable.&lt;/p&gt;
&lt;h1 id=&quot;history-repeats&quot;&gt;History repeats?&lt;/h1&gt;
&lt;p&gt;See, while seeking help on the UnRAID forums, I came across &lt;a href=&quot;https://forums.unraid.net/topic/140535-two-disks-died-after-612-upgrade/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;a post that I wrote in 2023&lt;/a&gt;, where I had two drives die on me after an UnRAID upgrade.&lt;/p&gt;
&lt;p&gt;Wait, two drives? Like, what I’m going through right now?&lt;/p&gt;
&lt;p&gt;This was back in June 2023, and I happened to be on a trip to Beijing, so I didn’t have physical access to my server. Thankfully, that particular trip ended after 2 weeks, and I was able to fly back to Korea and diagnose further, and it turned out to be the SAS to SATA cable going bad.&lt;/p&gt;
&lt;p&gt;For context, the Jonsbo N1 case is a mini-ITX case, so the insides are very, very cramped. So much so that a SAS cable sticking out the side has to be bent around to fit in the case. I even tried using a right-angle, 90-degree cable, but you still needed a bend to route the cable through to the area where the SATA connectors on the backplane were. Making matters worse, the SATA ends had to be right-angled, too, or else they’d snap off when the server was slid back into the outer casing and Jonsbo would void your warranty. And it’s hard to find sellers that will sell a cable that has both a 90-degree bend at the SAS end &lt;strong&gt;AND&lt;/strong&gt; the SATA ends.&lt;/p&gt;
&lt;p&gt;I’d replaced the cable and the drives had all spun back up again with zero problems. Until now, of course.&lt;/p&gt;
&lt;h1 id=&quot;supply-chain-issues&quot;&gt;&lt;a href=&quot;https://youtu.be/USBX2pIU71k?si=c2vsw6kMI6FQNwFq&amp;#x26;t=350&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;”Supply chain issues”&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;I decided to replace both the HBA card and the breakout cable in one go, so that if either one turned out to not be the culprit, I wouldn’t have to wait longer to get another shipment. Plus, it was not that expensive — a used card on AliExpress only cost about $30, and the cable only added a bit more to the total.&lt;/p&gt;
&lt;p&gt;Unfortunately, it was the worst possible timing to be shopping for replacement server parts. Because of the Chinese Lunar Year holiday, a lot of the AliExpress sellers were taking off days, for the entire week and then some. This meant that by the time the parts made it out of their warehouses and got shipped in a boat from China to Korea it would be mid- to late-February, which meant we’d be without our server for an entire month.&lt;/p&gt;
&lt;p&gt;While researching options, I came across those PCIe to SATA cards, with chipsets from ASMedia. They served essentially the same purpose as the HBA card — why couldn’t I use them?&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;ASMedia PCIe to SATA card listings on AliExpress&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;2175&quot; height=&quot;916&quot; src=&quot;/_astro/asmedia-pcie-to-sata-card-listings.Vu_noeUq_Zn8XSk.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;I thought I’d read something about avoiding those cards since they tended to drop the drives whenever the load increased and were generally unreliable, but it turned that I was thinking about those SATA port multiplier cards, which had Marvell chipsets and were best avoided at all costs. Regular PCIe to SATA cards seemed to be fine, though it looked like the cards &lt;a href=&quot;https://forums.unraid.net/topic/102010-recommended-controllers-for-unraid/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;based on the ASM1166 chipset needed a simple firmware update for it to work properly&lt;/a&gt;. But compared to an LSI HBA card, that wasn’t that big of a deal, since you needed to flash the firmware on LSI cards to get them into IT mode anyway. And the price was an absolute bargain — I paid perhaps $10 for the card, and like $2 for a 12-pack 90-degree SATA cable set, which was cheaper than the LSI option by half as much.&lt;/p&gt;
&lt;p&gt;So I picked up a card with the ASM1066 chipset from AliExpress, which immediately shipped because it came from AliExpress’s warehouses, and they seemed to ignore holidays. (The joys of capitalism.) Unfortunately, the port at Pyeongtaek (평택) that screened the packages for customs did not, so I only got the card during the first week of February, which was still way better than the expected ship date for the LSI card.&lt;/p&gt;
&lt;p&gt;When the card arrived, we discovered that it was absolutely perfect. It took up much less space than the previous LSI card, and the ports were angled towards the front of the case where the hard drives were, so there was absolutely zero bend or pressure on the SATA cables, which meant they wouldn’t fail that way.&lt;/p&gt;
&lt;p&gt;Once we got everything hooked up, the server was powered back on, the drives came back, I was able to start the resilvering process, and it was a happy ending, right? Right?!&lt;/p&gt;
&lt;h1 id=&quot;the-server-of-theseus&quot;&gt;The Server of Theseus&lt;/h1&gt;
&lt;p&gt;What do you do when you’ve replaced virtually everything about the server… and you still get slapped in the face with:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Screenshot of I/O errors from the server hard drives&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1556&quot; height=&quot;1018&quot; src=&quot;/_astro/io-error-theseus.wkzWfWE5_WxlmG.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;This was bad. Somehow, the problem persisted, even when I had:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Replaced the hard drives with brand-new ones&lt;/li&gt;
&lt;li&gt;Replaced the RAID card with a brand-new SATA card&lt;/li&gt;
&lt;li&gt;Replaced all the SATA cabling between the card and backplane&lt;/li&gt;
&lt;li&gt;Randomized the order of the hard drives connected to the backplane so as to rule out a bay issue&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Out of ideas, I &lt;a href=&quot;https://forums.unraid.net/topic/186431-io-errors-on-brand-new-hard-drives-cables-and-sata-controller-card&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;asked for help on the UnRAID forums&lt;/a&gt; again, since I had no idea where those ATA errors were coming from. &lt;a href=&quot;https://forums.unraid.net/topic/186431-io-errors-on-brand-new-hard-drives-cables-and-sata-controller-card/#findComment-1521969&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;@JorgeB on the forums said that the errors still looked like a connection problem, particularly on the power side&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Unfortunately, I didn’t have a spare PSU to test, but I had an idea. I called in and asked my brother to move the Molex connectors one row down, to see if perhaps the length was the issue. (By one row down, I mean the Molex cable was one long cable with four Molex plugs wired in parallel, and I was using the two outermost ones furthest away from the PSU.)&lt;/p&gt;
&lt;p&gt;He had one hell of a time getting the cable out of the connector because the connector on the backplane was in a hard-to-reach spot. I also wondered whether heat had deformed the connector, making it harder to remove and potentially causing an imperfect contact between the pin and connector. Unfortunately, I couldn’t tell over the grainy video call, and the photos didn’t help much, either.&lt;/p&gt;











&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;center&quot;&gt;&lt;img alt=&quot;The barely-visible Molex cable on the underside of the server&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1152&quot; height=&quot;1437&quot; src=&quot;/_astro/server-underside-grainy-photo.Dosb3qLi_Z1KFqw7.webp&quot; &gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;center&quot;&gt;&lt;em&gt;Can you make out the 4-wire Molex cable underneath the thick cables marked ‘ChingLung’?&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Finally, once the server was reassembled for the umpteenth time, he powered on the system and I started a preclear to see if the I/O issues had gone away.&lt;/p&gt;
&lt;h1 id=&quot;jumping-over-the-first-hurdle&quot;&gt;Jumping over the first hurdle&lt;/h1&gt;
&lt;p&gt;UnRAID’s pre-clear system is comprised of five different stages, but only three of them are the ones that access every single byte on the drive. And unfortunately (or fortunately?) for me, the drives that I was preclearing were 16 TB in size.&lt;/p&gt;
&lt;p&gt;It was a long and agonizing wait, but I decided to wait it out, because I didn’t want to risk another failure while rebuilding the array because of some latent power issue that decided to manifest again. So I let it run, and checked in periodically to monitor the progress.&lt;/p&gt;
&lt;p&gt;Each stage took around 20 hours to complete — 20 hours to either read or erase every single byte of the 16 TB drive.&lt;/p&gt;
&lt;p&gt;But thankfully, the preclear passed after running for a bit over 62 hours:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;####################################################################################################&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#                              Unraid Server Preclear of disk _____JFA                             #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#                            Cycle 1 of 1, partition start on sector 64.                           #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#                                                                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Step 1 of 5 - Pre-read verification:                  [20:44:44 @ 214 MB/s] SUCCESS            #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Step 2 of 5 - Zeroing the disk:                       [20:46:40 @ 213 MB/s] SUCCESS            #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Step 3 of 5 - Writing Unraid&apos;s Preclear signature:                          SUCCESS            #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Step 4 of 5 - Verifying Unraid&apos;s Preclear signature:                        SUCCESS            #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Step 5 of 5 - Post-Read verification:                 [20:44:42 @ 214 MB/s] SUCCESS            #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#                                                                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#                                                                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#                                                                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;####################################################################################################&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#       Cycle elapsed time: 62:16:35 | Total elapsed time: 62:16:36                                #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;####################################################################################################&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;####################################################################################################&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   S.M.A.R.T. Status (device type: default)                                                       #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#                                                                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   ATTRIBUTE                   INITIAL CYCLE 1 STATUS                                             #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Reallocated_Sector_Ct       0       0       -                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Power_On_Hours              3       65      Up 62                                              #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Reported_Uncorrect          0       0       -                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Airflow_Temperature_Cel     28      41      Up 13                                              #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Current_Pending_Sector      0       0       -                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Offline_Uncorrectable       0       0       -                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   UDMA_CRC_Error_Count        0       0       -                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#                                                                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#                                                                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;####################################################################################################&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#       Report genereated on: February 08, 2025 at 13:00:38                                        #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;####################################################################################################&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;--&gt; ATTENTION: Please take a look into the SMART report above for drive health issues.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;--&gt; RESULT: Preclear Finished Successfully!&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And a short while later, the second 16 TB disk precleared successfully, giving me some hope back:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;####################################################################################################&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#                              Unraid Server Preclear of disk _____DA5                             #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#                            Cycle 1 of 1, partition start on sector 64.                           #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#                                                                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Step 1 of 5 - Pre-read verification:                  [21:27:40 @ 207 MB/s] SUCCESS            #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Step 2 of 5 - Zeroing the disk:                       [21:30:35 @ 206 MB/s] SUCCESS            #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Step 3 of 5 - Writing Unraid&apos;s Preclear signature:                          SUCCESS            #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Step 4 of 5 - Verifying Unraid&apos;s Preclear signature:                        SUCCESS            #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Step 5 of 5 - Post-Read verification:                 [21:27:42 @ 207 MB/s] SUCCESS            #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#                                                                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#                                                                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#                                                                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;####################################################################################################&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#       Cycle elapsed time: 64:26:28 | Total elapsed time: 64:26:28                                #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;####################################################################################################&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;####################################################################################################&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   S.M.A.R.T. Status (device type: default)                                                       #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#                                                                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   ATTRIBUTE                   INITIAL CYCLE 1 STATUS                                             #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Reallocated_Sector_Ct       0       0       -                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Power_On_Hours              3       67      Up 64                                              #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Reported_Uncorrect          0       0       -                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Airflow_Temperature_Cel     28      41      Up 13                                              #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Current_Pending_Sector      0       0       -                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   Offline_Uncorrectable       0       0       -                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#   UDMA_CRC_Error_Count        0       0       -                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#                                                                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#                                                                                                  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;####################################################################################################&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#       Report genereated on: February 08, 2025 at 15:10:38                                        #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;####################################################################################################&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;--&gt; ATTENTION: Please take a look into the SMART report above for drive health issues.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;--&gt; RESULT: Preclear Finished Successfully!&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;rebuilding&quot;&gt;Rebuilding&lt;/h1&gt;
&lt;p&gt;The rebuild process was slightly more complicated than it had to be, because I’d opted to get drives of a bigger capacity to replace the “failed” ones.&lt;/p&gt;
&lt;p&gt;Now, UnRAID won’t let you have a data drive that is bigger than the parity drive, and the amount of available storage is just the sum of all the data drive capacities, which meant I wouldn’t have any extra storage space available right now by swapping out two out of the five 8 TB drives in my array with 16 TB ones. It just meant that I’d have to assign them as parity drives, and in the future if I ever got a drive that was bigger than 8 TB (but less than or equal to 16 TB, because of the parity drives) to replace one of the data drives then I’d see the increased storage space. As for now, it was mostly future-proofing.&lt;/p&gt;
&lt;p&gt;Unfortunately, one of the two drives that had failed was a data drive, and as I mentioned earlier, UnRAID wouldn’t let you have a data drive bigger than a parity drive, even if you wanted UnRAID to only use the first 8 TB and use the rest later once all the parity drives were upgraded.&lt;/p&gt;
&lt;p&gt;Thankfully, UnRAID has a special “parity-swap” procedure for instances like these: you assign the new drives as parity drives, and then move the other 8 TB parity drive into one of the data drive slots (where the failed data drive used to be). Then UnRAID recognizes that you’re trying to copy the parity data from the old drive to the new, and then rebuild the data drive on top of the old parity drive.&lt;/p&gt;
&lt;p&gt;I assigned the drives and clicked the copy button to start the process. The array isn’t accessible during the copy step, but I was fine with that as long as it didn’t crash during the procedure. My server had one final scare up its sleeve, though, just to get a last laugh:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Pushover notification from server during rebuild&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1440&quot; height=&quot;1597&quot; src=&quot;/_astro/rebuild-scare.DFarhLuD_Z6aLvx.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;This was just the “Fix Common Problems” plugin telling me that my array was already in a degraded state, but when I saw the “Errors” part I instantly thought the drives had dropped out again due to another brownout or something.&lt;/p&gt;
&lt;p&gt;Thankfully, nothing of that sort happened, and the parity-swap finished successfully after several hours. It actually took longer than each preclear step, probably because the 8 TB also slows down near the end, and we need the data from the 8 TB drive to populate the 16 TB one.&lt;/p&gt;
&lt;p&gt;I then kicked off the data and parity drive rebuild process, which could be done in one go. This took the longest, clocking in at 27 hours (!), but mercifully it passed as well:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Pushover notification from server letting me know that the parity sync and data rebuild is complete&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1440&quot; height=&quot;408&quot; src=&quot;/_astro/sync-complete.Db1j_vIN_Tzfmg.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;And with that, the server was finally brought back online after &lt;strong&gt;28 days&lt;/strong&gt;, concluding the outage.&lt;/p&gt;
&lt;h1 id=&quot;failure-analysis&quot;&gt;Failure Analysis&lt;/h1&gt;
&lt;p&gt;Given that futzing around with the Molex connector temporarily resolved the problem the first time around and permanently resolved it in the end, we can focus on the power-side of things.&lt;/p&gt;
&lt;p&gt;First, in terms of wattage, I was well within the specs of the system. The power supply was a SP750 model from Lian Li, and from what I can tell with the system specs, the most I ever needed with the parts in the NAS was perhaps 380 W. That is half of the rated wattage of the PSU, so a failure of it was rather unlikely.&lt;/p&gt;
&lt;p&gt;That left the single cable, which was carrying all the power to the backplane. When I first built this system, this was the only option, because Jonsbo’s backplane only had the two Molex ports, and also because Lian Li decided to only ship one Molex cable in their entire set of detachable cables.&lt;/p&gt;
&lt;p&gt;But did I overdrive the cables to the point of them failing? Well, let’s do some math.&lt;/p&gt;
&lt;p&gt;For starters, here are the specifications sheets for the two model types of the drives that I have in my NAS:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.seagate.com/www-content/datasheets/pdfs/exos-x16-DS2011-1-1904US-en_US.pdf&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Seagate Exos X16 16 TB&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://documents.westerndigital.com/content/dam/doc-library/en_us/assets/public/western-digital/product/internal-drives/wd-blue-hdd/product-brief-western-digital-wd-blue-pc-hdd.pdf&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Western Digital Blue 8 TB&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As previously mentioned, I have two 16 TB drives, and three 8 TB drives. Seagate’s specs say the maximum operating wattage of the drives is 10 W for each drive, although it doesn’t list the start-up peak power, which would probably be higher than 10 W. WD does list the peak amperage of the 12 V rail at 1.75 A, giving us around 21 W of peak start-up power. WD also mentions that the average power requirements for read and write is about 6.2 W. Scaling that up to Seagate’s power requirements linearly suggests that Seagate’s peak startup wattage is about 31 W, so we’ll assume it is around that number.&lt;/p&gt;
&lt;p&gt;This means that during startup, the drives in my NAS draw about 125 W, and during normal use with all drives spinning at once, the average power draw should be around 38.6 W.&lt;/p&gt;
&lt;p&gt;According to &lt;a href=&quot;https://linustechtips.com/topic/1384462-maximum-wattage-on-1x-molex-cable-with-4-connectors/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;a forum post on LTT&lt;/a&gt;, a single Molex cable should give me 120 W of power on the 12 V rail, which means I briefly exceed this rating upon starting up the server by about 5 W. But again, this is with us assuming that the peak startup wattage for the Seagate drives scales linearly from WD’s specifications, which is probably not the case. And during normal use, the power usage is well below the maximum capability of a typical Molex cable.&lt;/p&gt;
&lt;p&gt;So here’s my hypothesis on what happened. This server has been running non-stop since June 2022, which means it has been operational for nearly 3 years at this point. During the time, perhaps the load on the cable was slightly over the rated limit, causing slight amounts of heat to deform the connector and cause imperfect contact on the pins. Paired with the vibrations from the hard drives sitting directly on top of the backplane, the cable kept creeping out of the connector until at some point, the voltage sag from the imperfect contact was just too much for some of the hard drives to handle, and the most sensitive ones began to drop off from the server, causing the I/O errors.&lt;/p&gt;
&lt;p&gt;When the Molex plugs were moved one row over, the new plug was still in good condition and was able to make better contact with the backplane connector.&lt;/p&gt;
&lt;p&gt;There was one chance where I could’ve suspected this and have avoided the hassle of waiting for the new parts to arrive. The fact that two drives dropped out at the exact same time should’ve clued me into the fact that this was no drive failure. Even if they were from the same batch, drives don’t just up and die from the array in the same precise moment like that, due to manufacturing variations. Of course, this is all in hindsight, so I guess it’ll serve as a reference in the future if it ever happens again.&lt;/p&gt;
&lt;h1 id=&quot;lessons-learned&quot;&gt;Lessons learned&lt;/h1&gt;
&lt;p&gt;Unfortunately, if my hypothesis is correct, this is still a temporary solution. If the wattage is exceeding the cable specs by only a little, it would still work for maybe a year or two before developing the same problems.&lt;/p&gt;
&lt;p&gt;To solve this for good, I’m planning to buy and install two separate Molex cables from the PSU to the backplane, with the highest wire gauge I can obtain so that more power can be delivered over less resistance. This should help with the marginal heat generated, if any.&lt;/p&gt;
&lt;p&gt;But thankfully, the home server is finally back up, and ultimately I did not lose any data from this incident or have to restore from a backup. The redundancy part of my server functioned exactly as I planned.&lt;/p&gt;
&lt;p&gt;To reduce the downtime from waiting for parts next time, I plan to have at least one spare hard drive available as well. Since the Jonsbo N1 only houses 5 drives at once and I use all of the bays for the array, that means the drive will have to be a cold spare, but it’s faster than waiting weeks for the drives to arrive from China or wherever.&lt;/p&gt;
&lt;p&gt;(Note: I don’t think having more than one spare is a good idea, for a couple of reasons. Drives historically have become cheaper over time, and I still stand by the fact that the chances of two or more drives failing at once is extremely slim. And yes, I do realize the irony while writing this blog post, but again, this was not a problem with the actual hard drives, and if I had started with the cabling then I could’ve gotten away with purchasing no drives at all.)&lt;/p&gt;
&lt;p&gt;So there you go — if you’re chasing random I/O errors that seem to haunt you across different hardware, hopefully you can also fix it by closely inspecting the power situation of your system, and making sure that all the connectors tightly fit the inserted plugs!&lt;/p&gt;</content:encoded></item><item><title>Profiling with cargo flamegraph</title><link>https://ericswpark.com/blog/2025/2025-01-25-profiling-with-cargo-flamegraph/</link><guid isPermaLink="true">https://ericswpark.com/blog/2025/2025-01-25-profiling-with-cargo-flamegraph/</guid><pubDate>Sat, 25 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This will be a rather short blog post, because all I wanted to show off was this flamegraph, generated from one of the questions in the competitive programming course I’m taking:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;flamegraph&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1200&quot; height=&quot;822&quot; src=&quot;/_astro/flamegraph.DJ3oIKHL_20KA58.svg&quot; &gt;&lt;/p&gt;
&lt;p&gt;(Note: the SVG file is actually interactive, but you do need to right-click on the image and open it in a new tab!)&lt;/p&gt;
&lt;p&gt;I love looking at flamegraphs!&lt;/p&gt;
&lt;p&gt;Side-note: getting &lt;code&gt;cargo flamegraph&lt;/code&gt; running on Windows is slightly involved.&lt;/p&gt;
&lt;h1 id=&quot;windows-preparations&quot;&gt;Windows Preparations&lt;/h1&gt;
&lt;ol start=&quot;0&quot;&gt;
&lt;li&gt;If you have BitLocker enabled on your machine, back up the recovery key. You will need it in a sec. Most Windows machines nowadays ship with BitLocker enabled.&lt;/li&gt;
&lt;li&gt;Enable &lt;code&gt;dtrace&lt;/code&gt; functionality by running &lt;code&gt;bcdedit /set dtrace on&lt;/code&gt; in an elevated permissions terminal.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/microsoft/DTrace-on-Windows&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Download and run the installer from Microsoft’s official &lt;code&gt;dtrace&lt;/code&gt; repository&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Set the &lt;code&gt;_NT_SYMBOL_PATH&lt;/code&gt; system environment variable to the following (&lt;a href=&quot;https://learn.microsoft.com/en-us/windows/win32/dxtecharts/debugging-with-symbols#using-the-microsoft-symbol-server&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;see details on why here&lt;/a&gt;):&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;srv*c:\symbols*https://msdl.microsoft.com/download/symbols&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;Reboot your machine. Enter in the BitLocker recovery key when prompted.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&quot;linux-preparations&quot;&gt;Linux Preparations&lt;/h1&gt;
&lt;p&gt;On Linux, you do need the &lt;code&gt;linux-tools&lt;/code&gt; packages for your specific kernel version. I’m using NixOS, so I just had to run &lt;code&gt;nix shell nixpkgs#linuxKernel.packages.linux_latest_libre.perf&lt;/code&gt; to get just the &lt;code&gt;perf&lt;/code&gt; package that &lt;code&gt;flamegraph-rs&lt;/code&gt; requires. (No idea what the &lt;code&gt;libre&lt;/code&gt; part is for, but there wasn’t one without that suffix…) &lt;a href=&quot;https://github.com/flamegraph-rs/flamegraph?tab=readme-ov-file#installation&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Check out the official installation section of the README&lt;/a&gt; for distro-specific instructions.&lt;/p&gt;</content:encoded></item><item><title>Buffering by block in Rust</title><link>https://ericswpark.com/blog/2025/2025-01-23-buffering-by-block-in-rust/</link><guid isPermaLink="true">https://ericswpark.com/blog/2025/2025-01-23-buffering-by-block-in-rust/</guid><pubDate>Thu, 23 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I’m taking a competitive programming course this semester, and the platform we’re using (&lt;a href=&quot;https://kattis.com&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Kattis&lt;/a&gt;) apparently prioritizes fast I/O, to the point where some right answers will be rejected with a Time Limit Exceeded (TLE).&lt;/p&gt;
&lt;p&gt;I wanted to see if there was a way to squeeze out just a bit more read/write performance in Rust, and was surprised to learn that &lt;code&gt;stdout&lt;/code&gt; is line-buffered by default. This means that an innocent-looking code sample like the following writes slower to console than the theoretical speed limit:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;rust&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; std&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;time&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Instant&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;fn&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; main&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; time &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Instant&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;500000&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;        println!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;{}&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, i);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; duration &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; time&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;elapsed&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    println!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Time elapsed: {:?}&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, duration);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you run it, you’ll get an output like the following:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;(...)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;499997&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;499998&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;499999&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Time elapsed: 17.5983885s&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So why does this happen? Every time you call the &lt;code&gt;println!()&lt;/code&gt; macro, it emits a newline character (&lt;code&gt;\n&lt;/code&gt;) to &lt;code&gt;stdout&lt;/code&gt;, and every time &lt;code&gt;stdout&lt;/code&gt; sees a newline character it flushes its buffer. To flush the buffer, it executes a syscall that writes the contents of the buffer to the console, or to a file if there are redirects, whatever.&lt;/p&gt;
&lt;p&gt;The problem is, every time you execute a syscall there is some overhead because the processor has to change from user to kernel mode and shift around registers and so on. And we’re doing this 500k times! So the overhead stacks up.&lt;/p&gt;
&lt;p&gt;The solution is to stick a &lt;code&gt;BufWriter&lt;/code&gt; in front of the &lt;code&gt;stdout&lt;/code&gt;, which might seem weird and the exact opposite of what we’re trying to achieve (we wanted less buffering, right?), until you realize that &lt;code&gt;BufWriter&lt;/code&gt; holds the contents you are trying to write in a separate buffer. When we call &lt;code&gt;.flush()&lt;/code&gt; on &lt;code&gt;BufWriter&lt;/code&gt;, it dumps all of the contents in &lt;code&gt;stdout&lt;/code&gt; and calls &lt;code&gt;.flush()&lt;/code&gt; on it as well, which effectively makes &lt;code&gt;stdout&lt;/code&gt; block-buffered.&lt;/p&gt;
&lt;p&gt;I was initially skeptical of this approach and thought &lt;code&gt;stdout&lt;/code&gt; would just end up calling the write-syscall whenever it saw the newline character being passed through from &lt;code&gt;BufWriter&lt;/code&gt;’s buffer, but the performance improvements suggest otherwise.&lt;/p&gt;
&lt;p&gt;The modified code:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;rust&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; std&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;io;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; std&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;io&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;BufWriter&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Write&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; std&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;time&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Instant&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;fn&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; main&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; time &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Instant&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; output &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; io&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;stdout&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;lock&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; mut&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; buffer &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; BufWriter&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(output);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;500000&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;        writeln!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(buffer, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;{}&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, i)&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;unwrap&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    buffer&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;flush&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;unwrap&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; duration &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; time&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;elapsed&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    println!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Time elapsed: {:?}&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, duration);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And the results in case you thought I was lying:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;(...)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;499997&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;499998&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;499999&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Time elapsed: 3.7357123s&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So why doesn’t Rust just let us toggle between line-buffered and block-buffered for &lt;code&gt;stdout&lt;/code&gt;? Well, the &lt;a href=&quot;https://github.com/rust-lang/rust/issues/60673&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;issue exists on their GitHub issue tracker&lt;/a&gt;, but the &lt;a href=&quot;https://github.com/rust-lang/rust/pull/78515&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;associated pull request&lt;/a&gt; was closed back in 2022 and as a result the problem has been stuck like that for the past 3 years. (If you count the original issue that makes it nearly 6 years!)&lt;/p&gt;
&lt;p&gt;So until this becomes part of the language, the workaround above seems to be the best crate-free way of getting faster I/O in Rust. Of course, you could also try and get the raw, unbuffered &lt;code&gt;stdout&lt;/code&gt; using workarounds such as &lt;a href=&quot;https://github.com/rust-lang/rust/issues/58326#issuecomment-1802406085&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;this one by @WieeRd on GitHub&lt;/a&gt;, or do what &lt;a href=&quot;https://docs.rs/grep-cli/0.1.11/grep_cli/fn.stdout.html&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;ripgrep does with their conditional buffering&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If only Kattis allowed crates.&lt;/p&gt;</content:encoded></item><item><title>KT&apos;s outdated Wi-Fi, on Linux</title><link>https://ericswpark.com/blog/2025/2025-01-04-kts-outdated-wifi-on-linux/</link><guid isPermaLink="true">https://ericswpark.com/blog/2025/2025-01-04-kts-outdated-wifi-on-linux/</guid><pubDate>Sat, 04 Jan 2025 11:15:00 GMT</pubDate><content:encoded>&lt;p&gt;If you ever try and connect to a public Wi-Fi AP hosted by KT, perhaps at a Starbucks, with your Linux laptop, you may notice that the connection fails. For me, KDE prompted that the password was incorrect, when it wasn’t — my Android phone could connect to the AP just fine.&lt;/p&gt;
&lt;p&gt;If you looked at the logs, you would see something like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Dec 26 16:39:14 kernel: wlp3s0: associate with 06:09:b4:78:b3:13 (try 1/3)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Dec 26 16:39:14 kernel: wlp3s0: associate with 06:09:b4:78:b3:13 (try 2/3)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Dec 26 16:39:14 kernel: wlp3s0: RX AssocResp from 06:09:b4:78:b3:13 (capab=0x431 status=0 aid=1)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Dec 26 16:39:14 kernel: wlp3s0: associated&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Dec 26 16:39:14 wpa_supplicant[1408]: wlp3s0: Associated with 06:09:b4:78:b3:13&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Dec 26 16:39:14 wpa_supplicant[1408]: wlp3s0: CTRL-EVENT-EAP-STARTED EAP authentication started&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Dec 26 16:39:14 wpa_supplicant[1408]: wlp3s0: CTRL-EVENT-SUBNET-STATUS-UPDATE status=0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Dec 26 16:39:17 wpa_supplicant[1408]: wlp3s0: CTRL-EVENT-EAP-PROPOSED-METHOD vendor=0 method=25&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Dec 26 16:39:17 wpa_supplicant[1408]: wlp3s0: CTRL-EVENT-EAP-METHOD EAP vendor 0 method 25 (PEAP) selected&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Dec 26 16:39:17 wpa_supplicant[1408]: SSL: SSL3 alert: write (local SSL3 detected an error):fatal:protocol version&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Dec 26 16:39:17 wpa_supplicant[1408]: OpenSSL: openssl_handshake - SSL_connect error:0A000102:SSL routines::unsupported protocol&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Dec 26 16:39:17 wpa_supplicant[1408]: wlp3s0: CTRL-EVENT-EAP-FAILURE EAP authentication failed&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Dec 26 16:39:17 kernel: wlp3s0: disassociated from 06:09:b4:78:b3:13 (Reason: 23=IEEE8021X_FAILED)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Dec 26 16:39:17 wpa_supplicant[1408]: wlp3s0: CTRL-EVENT-DISCONNECTED bssid=06:09:b4:78:b3:13 reason=23&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Dec 26 16:39:17 wpa_supplicant[1408]: wlp3s0: CTRL-EVENT-SSID-TEMP-DISABLED id=0 ssid=&quot;KT_starbucks_Secure&quot; auth_failures=2 duration=31 reason=AUTH_FAILED&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Dec 26 16:39:17 wpa_supplicant[1408]: wlp3s0: Added BSSID 06:09:b4:78:b3:13 into ignore list, ignoring for 10 seconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Dec 26 16:39:17 wpa_supplicant[1408]: wlp3s0: BSSID 06:09:b4:78:b3:13 ignore list count incremented to 2, ignoring for 10 seconds&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is because KT’s Wi-Fi uses WPA2-Enterprise, and they’re still using a TLS version that is lower than TLS v1.2, &lt;a href=&quot;https://github.com/openssl/openssl/discussions/22642&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;which is disabled by default for security reasons in OpenSSL&lt;/a&gt;. This is even &lt;a href=&quot;https://wiki.archlinux.org/title/NetworkManager#WPA_Enterprise_connections_fail_to_authenticate_with_OpenSSL_%22unsupported_protocol%22_error&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;documented on the Arch wiki&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For some context, while TLS v1.0 and 1.1 were &lt;em&gt;only&lt;/em&gt; deprecated in March 2021, TLS v1.2 has been available since 2008, and TLS v1.3 (the latest) since 2018. There is really no excuse for why KT is still using the outdated version.&lt;/p&gt;
&lt;p&gt;Instead of overriding the settings and allowing an insecure version of TLS, my solution was to just use the open Wi-Fi with no authentication and then utilizing Tailscale’s excellent exit node feature to just secure my connection. Much less hassle and my connection is still secured from eavesdroppers.&lt;/p&gt;
&lt;p&gt;This is the same company that claims they’re the forerunner in AI solutions on television ads. I wonder if you guys feel any shame, KT?&lt;/p&gt;</content:encoded></item><item><title>Reverting DD-WRTed WRT32X back to stock</title><link>https://ericswpark.com/blog/2025/2025-01-04-reverting-dd-wrted-wrt32x-back-to-stock/</link><guid isPermaLink="true">https://ericswpark.com/blog/2025/2025-01-04-reverting-dd-wrted-wrt32x-back-to-stock/</guid><pubDate>Sat, 04 Jan 2025 11:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I found an old router we used to use back in China while digging through our moving boxes. At the time, I flashed DD-WRT on the thing because we used a VPN service named Astrill, which had a little DD-WRT applet that let you set up a network-wide VPN for the entire household (which meant we didn’t have to configure VPN on each device).&lt;/p&gt;
&lt;p&gt;Now that I was no longer in Internet-censorship-land, I decided to flash it again with OpenWrt. But the instructions online only cover how to flash OpenWrt from a stock system.&lt;/p&gt;
&lt;p&gt;Rather than risk bricking the router trying to manually &lt;code&gt;dd&lt;/code&gt; over the partitions, I decided to first flash the stock Linksys image onto the router and then convert it to OpenWrt from there. Unfortunately, &lt;a href=&quot;https://forum.dd-wrt.com/phpBB2/viewtopic.php?t=316094&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;the forum post on DD-WRT’s forums detailing how to go back to stock&lt;/a&gt; required you to grab a custom-made image that had a padded bit, so that DD-WRT’s image flasher would flash it correctly to the partition table. While the image was &lt;a href=&quot;https://forum.dd-wrt.com/phpBB2/viewtopic.php?p=1158363#1158363&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;available for download here&lt;/a&gt;, DD-WRT’s forum software would only allow downloads once you signed up for a forum account.&lt;/p&gt;
&lt;p&gt;I tried to sign up for a temporary account to download the pre-converted image, but it appears that DD-WRT’s forum is in a state of abandonment. The confirmation email to actually activate the account never arrived, and my email to the forum administrators went unanswered.&lt;/p&gt;
&lt;p&gt;Time to follow &lt;a href=&quot;https://forum.dd-wrt.com/phpBB2/viewtopic.php?p=1097563#1097563&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;the instructions on how to pad a stock image manually&lt;/a&gt;, then. While following the instructions, I didn’t realize that newer stock firmware images had a different partition layout, so the amount of data to pad was different. Of course, I only found out about this when I overwrote the partition and essentially soft-bricked the device. Thankfully, the A/B partition swap kicked in, and I was able to retry with the correctly converted image.&lt;/p&gt;
&lt;p&gt;So if you ever find yourself with a WRT32X flashed with DD-WRT and you want to go back to stock or use OpenWrt on it, &lt;a href=&quot;https://archive.org/details/wrt32x-padded-image-dd-wrt-to-stock&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;you can use this image that I converted here&lt;/a&gt;. (&lt;a href=&quot;https://files.catbox.moe/qd2dot.img&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Alternate mirror on Catbox&lt;/a&gt;) Don’t forget to verify your download with the SHA256 checksum:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;fdf5914007d02ee86a2a93d3e20bd677403ec1c9849d8126840c79126507239c&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As for the router, I don’t think I’ll be using it. I successfully flashed both partitions with OpenWrt, but the open-source &lt;code&gt;mwlwifi&lt;/code&gt; driver and firmware won’t let you adjust power levels, which means it’ll blast out the 2.4 GHz signal at full tilt and drown out the 5 GHz signal:&lt;/p&gt;
&lt;iframe src=&quot;https://tilde.zone/@ericswpark/113742272920262190/embed&quot; class=&quot;mastodon-embed max-w-full border-0 mx-auto&quot; width=&quot;800&quot; allowfullscreen=&quot;allowfullscreen&quot;&gt;&lt;/iframe&gt; &lt;script src=&quot;https://tilde.zone/embed.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
&lt;p&gt;Maybe the firmware could be reverse-engineered one day?&lt;/p&gt;</content:encoded></item><item><title>I migrated my site to Astro!</title><link>https://ericswpark.com/blog/2024/2024-12-29-i-migrated-my-site-to-astro/</link><guid isPermaLink="true">https://ericswpark.com/blog/2024/2024-12-29-i-migrated-my-site-to-astro/</guid><pubDate>Sun, 29 Dec 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you’re reading this blog post, it means the new site based on &lt;a href=&quot;https://astro.build/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Astro&lt;/a&gt; is now live!&lt;/p&gt;
&lt;h1 id=&quot;announcements&quot;&gt;Announcements&lt;/h1&gt;
&lt;p&gt;Here are all the breaking changes regarding my site that you should probably know about:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;If you are using an RSS reader&lt;/strong&gt; to subscribe to my blog, you need to update your URL from &lt;code&gt;https://ericswpark.com/blog/index.xml&lt;/code&gt; to &lt;code&gt;https://ericswpark.com/blog/rss.xml&lt;/code&gt;. You should’ve also seen a notice on your reader if you are subscribed to the old feed URL.&lt;/li&gt;
&lt;li&gt;Some subfolders are now children to the &lt;code&gt;/pages&lt;/code&gt; subfolder, such as &lt;code&gt;/pages/tools&lt;/code&gt; and &lt;code&gt;/pages/fun&lt;/code&gt;. I’ve set up redirect notices, but please update your bookmarks as soon as possible.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;motivation&quot;&gt;Motivation&lt;/h1&gt;
&lt;p&gt;I’ve gone through so many website frameworks that it’s honestly very hard to keep track. I used WordPress, Ghost, Jekyll and then finally settled on &lt;a href=&quot;https://gohugo.io&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Hugo&lt;/a&gt; in 2021. For the last two frameworks, I used GitHub Pages for hosting, because I’m a student and it’s hard to beat the price of free.&lt;/p&gt;
&lt;p&gt;When I settled on Hugo, I thought I’d stay on for the next couple of years. While the entire website worked okay, I was dissatisfied with a couple of things.&lt;/p&gt;
&lt;p&gt;For starters, because I was (and still am) quite terrible at design, I decided to use a pre-made theme called &lt;a href=&quot;https://github.com/lxndrblz/anatole/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;anatole&lt;/a&gt;. While I am grateful that I was able to use it, the site built on top didn’t really feel like my own creation, even as I tried to style it my way.&lt;/p&gt;
&lt;p&gt;Speaking of styling things, it was quite difficult to do with Hugo. Hugo was built on top of Google’s Go language, and it has its unique templating syntax. The idea of having to learn it to make even the smallest changes to my website put me off from actually customizing the look and feel of my website for a very long time.&lt;/p&gt;
&lt;p&gt;But I liked the fact that I could just write for my blog by making a Markdown file, committing it, and pushing the changes to GitHub. Hugo, and the anatole theme I was using, had good support for internationalization (i18n), which I needed because I also translate my blog posts into Korean. This was so simple, in fact, that I was able to continue to post on my blog in the military back in 2021-23, by using my iPad to write Markdown in &lt;a href=&quot;https://workingcopy.app/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Working Copy&lt;/a&gt; and pushing it to GitHub.&lt;/p&gt;
&lt;p&gt;However, the final limitation that had me fishing for alternatives was when I tried to add some JavaScript to my website. I had a bunch of utilities, and I’d gotten by with vanilla JS by just stuffing them inside &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags and forcing Hugo to render raw HTML inside my Markdown files (&lt;a href=&quot;https://github.com/gohugoio/hugo/issues/6581&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;which was gated behind a config option that started with a scary &lt;code&gt;unsafe&lt;/code&gt; keyword for no coherent reason&lt;/a&gt;). But as the scope of my tools and pages grew, I had to include more features and dependencies, and that meant using a JS bundler to compile my JS into the final form.&lt;/p&gt;
&lt;p&gt;This was not a built-in feature for Hugo. I had to bolt on compiling JS with &lt;code&gt;webpack&lt;/code&gt;, and while it sort of worked, it made the build process more convoluted. Worse yet, I couldn’t figure out how to neatly organize my source files and have webpack cleanly compile them into bundled files for Hugo to pick up. And the fact that I still had to learn the templating language if I wanted any changes made signalled to me that it was perhaps time to move on.&lt;/p&gt;
&lt;h1 id=&quot;first-try-nextjs&quot;&gt;First try: NextJS!&lt;/h1&gt;
&lt;p&gt;I had some experience using NextJS for some of my personal and college projects, and thought it would be a perfect fit for my new website.&lt;/p&gt;
&lt;p&gt;Unfortunately, I was about 40% complete with the migration when I realized that the built-in MDX compiler did not support relative imports for images. &lt;a href=&quot;https://github.com/hashicorp/next-mdx-remote&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Nor did the &lt;code&gt;next-mdx-remote&lt;/code&gt; repo support it&lt;/a&gt;. This was a problem, because I already had a huge collection of blog posts (67 of them, or 134 counting the Korean translations) written in Markdown, with images nestled in each folder for easy relative imports:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;blog/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  2024/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    2024-12-29-i-migrated-my-site-to-astro/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      en.mdx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      ko.mdx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      imgA.png&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For some context, this meant I couldn’t do something like this in &lt;code&gt;en.mdx&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;jsx&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; imgA &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;#39;./imgA.png&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;I&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; migrated my site to &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Astro&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Image&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; src&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{imgA}&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead, I would’ve had to move all of my images to the &lt;code&gt;public/&lt;/code&gt; directory, which was very messy and not something I was willing to put up with.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/hashicorp/next-mdx-remote&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;mdx-bundler&lt;/code&gt; looked like a potential solution&lt;/a&gt;, but it required having a separate bundler as a build step, and that reminded me too much of Hugo and the &lt;code&gt;webpack&lt;/code&gt; situation.&lt;/p&gt;
&lt;p&gt;At this point, the members of the &lt;a href=&quot;https://www.purduehackers.com/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Purdue Hackers&lt;/a&gt; Discord couldn’t recommend Astro enough, so I decided to try that next, even though I was really bummed that I couldn’t use something I was familiar with. But hey, maybe Astro is better?&lt;/p&gt;
&lt;p&gt;(spoiler alert: yes it is, because you’re reading this blog post)&lt;/p&gt;
&lt;h1 id=&quot;a-rough-start-to-the-migration&quot;&gt;A rough start to the migration&lt;/h1&gt;
&lt;p&gt;I got started with the migration back in November 29th, so the entire process took exactly one month. But I swear that there is a good reason for why it took so long.&lt;/p&gt;
&lt;p&gt;I had a couple of requirements for the migration:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;All URLs must stay exactly the same. If there are any changes, I should be able to set up redirects or notices.&lt;/li&gt;
&lt;li&gt;The RSS feed should pick up where it left off, without any disturbances to the subscribed readers.&lt;/li&gt;
&lt;li&gt;All features must come over from the old website, including the theme switcher, language switcher, &lt;a href=&quot;https://giscus.app/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Giscus comments&lt;/a&gt;, and so on.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Almost immediately, I hit a roadblock. Astro would refuse to render my previous RSS feed URL at &lt;code&gt;https://ericswpark.com/blog/index.xml&lt;/code&gt;. I filed &lt;a href=&quot;https://github.com/withastro/astro/issues/12675&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;a bug report&lt;/a&gt;, but it sat dormant until this week. After some back and forth with Astro core maintainers, I figured out where the bug was coming from &lt;a href=&quot;https://github.com/withastro/astro/pull/12815&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;and submitted a pull request&lt;/a&gt;. Even though a member on the Astro Discord said they’d look into the pull request, as of writing this blog post it still hasn’t been merged, but it’s okay because &lt;code&gt;patch-package&lt;/code&gt; is a thing and I can just remove the patch whenever they get around to merging it.&lt;/p&gt;
&lt;p&gt;I decided that requirement #2 could be relaxed a little, and decided to move the URL from &lt;code&gt;index.xml&lt;/code&gt; to &lt;code&gt;rss.xml&lt;/code&gt;. It seemed more appropriate, and I could add more feed formats in the future, like &lt;code&gt;rss.json&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;After finals week passed, I continued work on the website migration. It took a while, because I had to write mostly everything from scratch. All the components had to be written for things like GitHub Gist embeds, the styling had to be written with CSS (although &lt;a href=&quot;https://tailwindcss.com/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Tailwind CSS&lt;/a&gt; really helped here), and routing — especially i18n routing — had to be set up properly and to my liking.&lt;/p&gt;
&lt;p&gt;But when you get past all of that?&lt;/p&gt;
&lt;h1 id=&quot;the-best-of-all-worlds&quot;&gt;The best of all worlds&lt;/h1&gt;
&lt;p&gt;The really neat thing about Astro is that mostly everything I can think of has first-class support. You want MDX? &lt;a href=&quot;https://docs.astro.build/en/guides/integrations-guide/mdx/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Astro has it.&lt;/a&gt; Want code syntax highlighting? &lt;a href=&quot;https://docs.astro.build/en/guides/syntax-highlighting/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Astro has it.&lt;/a&gt; Tailwind CSS? &lt;a href=&quot;https://docs.astro.build/en/guides/integrations-guide/tailwind/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Astro has it.&lt;/a&gt; Want to add React? &lt;a href=&quot;https://docs.astro.build/en/guides/integrations-guide/react/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Sure.&lt;/a&gt; Vue on top? &lt;a href=&quot;https://docs.astro.build/en/guides/integrations-guide/vue/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Why not.&lt;/a&gt; Server-side rendering? &lt;a href=&quot;https://docs.astro.build/en/guides/on-demand-rendering/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Config switch.&lt;/a&gt; Static site generation? The default. Their documentation is fantastic and their &lt;code&gt;.astro&lt;/code&gt; files were just JS extended a bit with a frontmatter-like section, so I was accustomed to the framework in no time at all.&lt;/p&gt;
&lt;p&gt;And because I could write the CSS from scratch, I had greater flexibility and freedom to customize the site however I wanted. (That being said, I’m still terrible at design. But I’m still proud of the homepage.) And Astro does some pretty nifty scoped styling, so that rules don’t conflict with each other. Global styles can still reach over and threaten to ruin your day, but you can just dial up the specificity to avoid that.&lt;/p&gt;
&lt;p&gt;And despite that snag I hit in the very beginning of the migration, the rest of it went very smoothly, and I was able to hit all the requirements that I set out for myself!&lt;/p&gt;
&lt;h1 id=&quot;comparisons&quot;&gt;Comparisons&lt;/h1&gt;
&lt;p&gt;If you’re curious, here are some of the before-and-after shots:&lt;/p&gt;

&lt;script type=&quot;module&quot; src=&quot;/home/runner/work/ericswpark.github.io/ericswpark.github.io/src/components/embeds/ImgComparison.astro?astro&amp;type=script&amp;index=0&amp;lang.ts&quot;&gt;&lt;/script&gt; &lt;img-comparison-slider class=&quot;dark:hidden&quot;&gt; &lt;img slot=&quot;first&quot; src=&quot;/_astro/before-home-light-en.D9BHeVWb.png&quot; width=&quot;3072&quot; height=&quot;1470&quot; loading=&quot;lazy&quot; alt=&quot;Homepage before (light mode)&quot;&gt; &lt;img slot=&quot;second&quot; src=&quot;/_astro/after-home-light-en.Ba4iva00.png&quot; width=&quot;3072&quot; height=&quot;1470&quot; loading=&quot;lazy&quot; alt=&quot;Homepage after (light mode)&quot;&gt; &lt;/img-comparison-slider&gt;
 &lt;img-comparison-slider class=&quot;hidden dark:block&quot;&gt; &lt;img slot=&quot;first&quot; src=&quot;/_astro/before-home-dark-en.BLa0Ug6s.png&quot; width=&quot;3072&quot; height=&quot;1470&quot; loading=&quot;lazy&quot; alt=&quot;Homepage before (dark mode)&quot;&gt; &lt;img slot=&quot;second&quot; src=&quot;/_astro/after-home-dark-en.dozx4IIh.png&quot; width=&quot;3072&quot; height=&quot;1470&quot; loading=&quot;lazy&quot; alt=&quot;Homepage after (dark mode)&quot;&gt; &lt;/img-comparison-slider&gt;
&lt;br/&gt;

 &lt;img-comparison-slider class=&quot;dark:hidden&quot;&gt; &lt;img slot=&quot;first&quot; src=&quot;/_astro/before-blog-post-light-en.aTRa-4LT.png&quot; width=&quot;3072&quot; height=&quot;1470&quot; loading=&quot;lazy&quot; alt=&quot;Blog post example before (light mode)&quot;&gt; &lt;img slot=&quot;second&quot; src=&quot;/_astro/after-blog-post-light-en.C0phuaWB.png&quot; width=&quot;3072&quot; height=&quot;1470&quot; loading=&quot;lazy&quot; alt=&quot;Blog post example after (light mode)&quot;&gt; &lt;/img-comparison-slider&gt;
 &lt;img-comparison-slider class=&quot;hidden dark:block&quot;&gt; &lt;img slot=&quot;first&quot; src=&quot;/_astro/before-blog-post-dark-en.D2-vkG3S.png&quot; width=&quot;3072&quot; height=&quot;1470&quot; loading=&quot;lazy&quot; alt=&quot;Blog post example before (dark mode)&quot;&gt; &lt;img slot=&quot;second&quot; src=&quot;/_astro/after-blog-post-dark-en.C51KaDb-.png&quot; width=&quot;3072&quot; height=&quot;1470&quot; loading=&quot;lazy&quot; alt=&quot;Blog post example after (dark mode)&quot;&gt; &lt;/img-comparison-slider&gt;
&lt;p&gt;In particular, I’m very pleased about the following bits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The language switcher is one click instead of two. It also asks properly if you want to be redirected to the main page of the other locale if there is no translated version, instead of just disappearing, which is what happened on my old site.&lt;/li&gt;
&lt;li&gt;The theme switcher lets you toggle to an automatic mode. It’s a bit hidden, though — you need to right-click on it twice, or go to the About page and scroll down to the “Site Settings” section.&lt;/li&gt;
&lt;li&gt;The homepage. Did I mention the homepage yet? Oh god I love hover effects&lt;/li&gt;
&lt;li&gt;The footer and the whimsical blurb text.&lt;/li&gt;
&lt;li&gt;Much more efficient use of space, and the focus on content. I didn’t like having the sidebar taking up space while I read through the content on my blog post, and now you don’t have to suffer through that either.&lt;/li&gt;
&lt;li&gt;And with Astro, I can extend this site however and whenever I want. I already went through just how &lt;em&gt;flexible&lt;/em&gt; this darn framework is.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Of course, the site will always be a work-in-progress. But now that the big migration is over, I can finally get back to writing more blog posts that I’ve been putting in my to-do list.&lt;/p&gt;
&lt;h1 id=&quot;thats-it&quot;&gt;That’s it!&lt;/h1&gt;
&lt;p&gt;I hope you enjoy the new site! If you have any suggestions or feedback, please reach out to be via email. In particular, please report any broken links, although I am pretty sure I’ve brought over all of them as I absolutely hate linkrot.&lt;/p&gt;
&lt;p&gt;Also, I’ve taken the opportunity during this migration to hide a little Easter egg on the site. I personally think it’s cute. Feel free to let me know via email or Discord if you find it! (And no it’s not the blurb text in the footer.)&lt;/p&gt;</content:encoded></item><item><title>Now on Bluesky</title><link>https://ericswpark.com/blog/2024/2024-11-24-now-on-bluesky/</link><guid isPermaLink="true">https://ericswpark.com/blog/2024/2024-11-24-now-on-bluesky/</guid><pubDate>Sun, 24 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://bsky.app/profile/ericswpark.com&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;I am now on Bluesky!&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Couple of things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Love how they do domain verification. Sure, Mastodon’s way of checking profile linkback is cool, too, but adding an actual DNS record feels “official”. Maybe it doesn’t scale very well, but they provide an alternate in the form of a &lt;code&gt;.well-known&lt;/code&gt; file.&lt;/li&gt;
&lt;li&gt;Interface is exactly like Twitter/X, but you don’t have to see Nazis. Sold!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Overall, this really seems like the replacement for Twitter. While Mastodon had some really innovative ideas in terms of federation and what not, the overall structure of it, coupled with the card column layout, was too confusing for normal people.&lt;/p&gt;
&lt;p&gt;Now here’s to hoping it stays this way. Third time’s the charm?&lt;/p&gt;</content:encoded></item><item><title>The Esc key bug in games with the CJK IMEs</title><link>https://ericswpark.com/blog/2024/2024-11-19-the-esc-key-bug-in-games-with-cjk-imes/</link><guid isPermaLink="true">https://ericswpark.com/blog/2024/2024-11-19-the-esc-key-bug-in-games-with-cjk-imes/</guid><pubDate>Tue, 19 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Recently, I’ve been playing through a really old shooter game named “Spec Ops: The Line”. Being a very old game, it has a bunch of weird quirks, such as some parts of the game being tied to the frame rate and being impossible to beat if you have a high refresh rate monitor, or the fullscreen mode completely screwing up the resolutions of the secondary monitors, etc.&lt;/p&gt;
&lt;p&gt;But one bug that has seriously nagged me is that I can’t pause the game. Whenever I press the Esc key to bring up the pause menu, the pause menu flashes for a quarter of a second and then blinks out again. The only workaround I found was to Alt-Tab out of the game, then Alt-Tab back in, then quickly hit the Esc key twice while the game redrew onto the screen.&lt;/p&gt;
&lt;p&gt;Unfortunately, you can’t really save and quit the game without going through the pause menu, so I tried to figure out what was going on. For some reason, I couldn’t find much information about it online. Given that SOTL had a recent surge of eyeballs on it when it was delisted from Steam, I knew that if it was a widespread issue then people must have posted about it online. The fact that there wasn’t any coverage about it, as far as I could tell, told me that it was a quirk with how the game interacted with my environment and system.&lt;/p&gt;
&lt;p&gt;I first tried unplugging all external keyboards and mice, in case Windows was doing something weird with multiple input devices. That didn’t help. I then tried switching my input method editor from Korean to English, and that immediately fixed it. &lt;em&gt;Wait, what?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;So I looked online, and it seems this affects other games, too:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.reddit.com/r/RocketLeague/comments/18yxl2z/anyone_have_a_thing_where_when_you_press_escape/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Rocket League&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.reddit.com/r/Borderlands2/comments/13wrzfb/esc_key_not_working_bug_fix/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Borderlands 2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But why? I don’t know, but here is my best guess:&lt;/p&gt;
&lt;p&gt;With a CJK (Chinese, Japanese, Korean) IME (input method editor), the editor must chain together a series of keypresses that the user inputs to write a single character, unlike other languages like English. For example, to type the Korean letter 가, you press the “r” key and the “k” key on the keyboard, but you get one character on the screen.&lt;/p&gt;
&lt;p&gt;So how does the IME know whether the user meant to type 가, or ㄱ and ㅏ separately? One way that users can do this is to press the escape key after pressing the first key. This tells the IME to return to the alpha character input mode, awaiting the first keypress for the next string of keypresses to construct another character.&lt;/p&gt;
&lt;p&gt;My hunch is when the IME emits the event for the Esc key, it also emits the event that lets the program running that it has returned to this state that awaits the alpha character input. This would allow programs to stop displaying the underline under the character being currently modified, to let the user know that successive keypresses will be interpreted as a new character. For game engines that are listening for the Esc key, they interpret it as two presses on the Esc key in succession, even though the Esc key has been pressed only once.&lt;/p&gt;
&lt;p&gt;Unfortunately, I could not reproduce this with other keycode capturing software like &lt;a href=&quot;https://www.nirsoft.net/utils/keyboard_state_view.html&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;KeyboardStateView for Windows&lt;/a&gt;, so it might just be a case of bad game engine code in some games where they never thought to test it on CJK IMEs. A pretty strange bug!&lt;/p&gt;</content:encoded></item><item><title>iPhone battery swap review: Apple sent me someone else&apos;s SIM?</title><link>https://ericswpark.com/blog/2024/2024-10-07-iphone-battery-swap-review-apple-sent-me-soneone-elses-sim/</link><guid isPermaLink="true">https://ericswpark.com/blog/2024/2024-10-07-iphone-battery-swap-review-apple-sent-me-soneone-elses-sim/</guid><pubDate>Mon, 07 Oct 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;So about a week ago, I sent in my iPhone for a mail-in repair because my battery health had dropped to 80%. (It was actually lower than that, with diagnostics showing the battery at around 77%, but for some reason the settings page stubbornly displayed 80%, as if taunting the fact that my AppleCare+ was about to run out. Thankfully, the customer agent saw the same value as I did through diagnostics, and I was approved the swap.)&lt;/p&gt;
&lt;p&gt;A week later and it’s today, and I got my phone back. The itemized receipt was the first thing that I saw out of the box, and it showed that Apple replaced my battery and the &lt;em&gt;ambient light sensor&lt;/em&gt; for some reason:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Itemized receipt showing the item number of the parts that were replaced&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;2568&quot; height=&quot;2175&quot; src=&quot;/_astro/itemized-receipt.j2r5QbpV_RuIe6.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;My only guess is either the sensor was damaged during repair, or it was damaged in such a way that it worked okay during regular use but failed diagnostics. I certainly didn’t notice the light sensor misbehaving while I used it for the last two years.&lt;/p&gt;
&lt;p&gt;When I opened up the enclosed box-within-a-box that had my actual iPhone, I noticed that they’d removed the tempered glass screen protector, which was understandable since they had to remove the screen to get to the battery. However, they’d manhandled the removal, because the border of the frame showed gouges where they inserted their metal pry tool to lever the display up:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Gouges in the frame of the iPhone, near the bottom of the screen&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;3000&quot; height=&quot;4000&quot; src=&quot;/_astro/damaged-frame.C7mUgmup_Z2lpjwX.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;Sure, it’s not noticeable unless you look really closely, but I’m still bummed about it because I took very good care of my device, and it was in pristine condition when I sent it in. Anybody would expect their iPhone back in a similar condition they sent it out, especially if it went to the official repair center. And before you start to defend Apple in the comments, ask yourself if you’d say the same thing if a third-party repair center made the same marks on your phone?&lt;/p&gt;
&lt;p&gt;Anyway, that wasn’t the most surprising thing I found with the repaired phone. I booted it up, and the initial setup wizard greeted me, because of course they’d wiped the device. (It’s okay, I have a backup, obviously.) But as I went through the setup process, I noticed the signal strength meter showed bars, and the 5G Ultra Wideband text showed up next to it. But I hadn’t even swapped in my SIM card yet…?&lt;/p&gt;
&lt;p&gt;Turns out, someone at Apple made a mistake, and &lt;em&gt;put in someone else’s T-Mobile SIM card in my repaired iPhone&lt;/em&gt;. I know the iPhone itself was my original iPhone, because the serial number matched what I wrote down previously.&lt;/p&gt;
&lt;p&gt;I called up T-Mobile and explained the situation, and the agent said I was free to toss the SIM as the owner of the SIM would probably just request another SIM. Still, I plan on holding onto it for a while just in case Apple asks for it back, but really this shouldn’t happen in the first place. Also, if you’re sending in any of your devices for repair, remove all accessories and especially your SIM card for precisely this reason! You never know who might get hold of it, and nothing good can come out of someone having access to your number.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;: battery swapped, health 100%, frame damaged, and Apple gave me someone’s number just in case I was lonely. 6/10?&lt;/p&gt;</content:encoded></item><item><title>Shipping packages from Korea to the US with EMS Premium</title><link>https://ericswpark.com/blog/2024/2024-09-25-shipping-packages-from-korea-to-the-us-with-ems-premium/</link><guid isPermaLink="true">https://ericswpark.com/blog/2024/2024-09-25-shipping-packages-from-korea-to-the-us-with-ems-premium/</guid><pubDate>Wed, 25 Sep 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Recently I needed to get a phone shipped from South Korea to here in Indiana, US. My plan was to purchase the phone online and get it shipped to our house, then ask a family member to ship it over via the post office. Sounds simple enough, right?&lt;/p&gt;
&lt;p&gt;For starters, the local post office (EMS) refused to send the phone because it contained a lithium battery. I searched this up, and it seemed like the alternative was to use a much more expensive service like DHL or FedEx, or use something called EMS Premium. It was an option being offered by UPS Korea in conjunction with Korea’s post system, and the price seemed pretty reasonable.&lt;/p&gt;
&lt;p&gt;To ship a phone with EMS Premium, the family member had to fill out a bunch of forms. One of them asked for the phone’s IMEI, the model number, and serial number. They also asked us to sign a form saying that they were not responsible for any damages caused to the phone. (We were allowed, however, to purchase insurance, in case the package itself was lost during transit.) So in case you try something similar, make sure to use plenty of bubble wrap and other soft materials.&lt;/p&gt;
&lt;p&gt;All in all, it cost 54,000 won (about $40.62 as of writing this post) for the base shipping price, along with 10,000 won (about $7.52 as of writing this post) for the added lost shipping insurance.&lt;/p&gt;
&lt;p&gt;Here’s the timeline, with local time specified for each event:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2024-09-11 10:31 - Package is received by the local post office in Korea&lt;/li&gt;
&lt;li&gt;2024-09-11 13:22 - Package leaves local post office&lt;/li&gt;
&lt;li&gt;2024-09-11 14:36 - Package arrives at central hub in the eastern part of Seoul&lt;/li&gt;
&lt;li&gt;2024-09-11 15:31 - Package leaves the central hub&lt;/li&gt;
&lt;li&gt;2024-09-11 19:59 - Package arrives at the international post warehouse at Incheon Airport&lt;/li&gt;
&lt;li&gt;2024-09-11 23:21 - Package handed to 3rd party courier (UPS in this case)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For some reason, the package info didn’t update for a very long time after that last entry. I got worried, but it turned out that there was a changeover from the tracking code that EMS Premium used to the UPS one.&lt;/p&gt;
&lt;p&gt;The changeover itself took like two days, which was a bit annoying but not the end of the world. Once the new tracking number had been generated the EMS Premium site started to redirect me to UPS’s website with the new code:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2024-09-13 12:19 - A label is created with UPS, but they don’t have the package yet&lt;/li&gt;
&lt;li&gt;2024-09-13 13:54 - Package arrives at UPS facilities at Incheon, Korea&lt;/li&gt;
&lt;li&gt;2024-09-13 13:59 - Package leaves UPS facilities&lt;/li&gt;
&lt;li&gt;2024-09-13 14:04 - Package arrives at another UPS facility at Incheon, Korea&lt;/li&gt;
&lt;li&gt;2024-09-13 15:06 - Package awaits final clearance from customs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Again, another long wait. I was rather nervous, because it was Friday, and Monday marked the start of Chuseok, a national holiday in South Korea. I thought that maybe UPS and/or the customs at Incheon Airport wouldn’t operate during holidays or weekends, but it turned out that they did, because the package started to move once more:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2024-09-15 01:30 - Package leaves from the facility&lt;/li&gt;
&lt;li&gt;2024-09-15 05:52 - Package cleared by customs and is being transported&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At this point, I was pretty sure that the package had made it onto a plane. I looked online, and it seemed like all packages leaving Korea headed for the US passed through a UPS facility in Anchorage.&lt;/p&gt;
&lt;p&gt;Unfortunately, for cargo flights, it’s hard to track exactly which flight it is, as some flight radar sites online disagree on the exact schedule and positioning of each route. I then found &lt;a href=&quot;https://www.flightradar24.com/data/flights/5x6099#371a0a67&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;flight 5X6099 on Flightradar24&lt;/a&gt;, which seemed like it had departed at 1:30 AM, exactly the time listed on the tracking page. Sure enough, the flight landed at 3:35 PM in Anchorage, and the tracking resumed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2024-09-14 15:52 - Package arrives at UPS facility in Anchorage&lt;/li&gt;
&lt;li&gt;2024-09-14 17:51 - Package leaves UPS facility&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Another time zone change, this time to Kentucky.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2024-09-15 04:13 - Package arrives at UPS facility in Louisville, Kentucky&lt;/li&gt;
&lt;li&gt;2024-09-15 10:29 - Import scan at facility&lt;/li&gt;
&lt;li&gt;2024-09-15 14:43 - Package leaves UPS facility&lt;/li&gt;
&lt;li&gt;2024-09-15 17:10 - Package arrives at UPS facility in Indianapolis, Indiana&lt;/li&gt;
&lt;li&gt;2024-09-16 07:17 - Processing at local post office&lt;/li&gt;
&lt;li&gt;2024-09-16 08:54 - Out for delivery&lt;/li&gt;
&lt;li&gt;2024-09-16 12:42 - Delivered&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In summary, it took 6 days in total for the package to arrive from Korea to the US. While the box was looking a bit squashed, the actual contents inside were undamaged, right down to the glass screen protector that I’d included.&lt;/p&gt;
&lt;p&gt;The downsides are that the package contents aren’t insured for damage, like I mentioned previously, and as your package grows in physical dimensions and weight the price balloons exponentially. Even if you don’t opt for EMS Premium because whatever you’re sending doesn’t contain lithium batteries, it would’ve cost nearly 100,000 won (around $74 as of writing this) to send a box filled with tools like calipers and Korean snacks.&lt;/p&gt;
&lt;p&gt;Still, it’s cheaper than a round-trip flight. So unless you or someone you know have a scheduled flight coming up, perhaps this is the best and only way to get packages with potentially flammable goods inside across the globe.&lt;/p&gt;</content:encoded></item><item><title>Django: Disallow Postgres-specific fields</title><link>https://ericswpark.com/blog/2024/2024-08-05-django-disallow-postgres-specific-fields/</link><guid isPermaLink="true">https://ericswpark.com/blog/2024/2024-08-05-django-disallow-postgres-specific-fields/</guid><pubDate>Mon, 05 Aug 2024 09:52:00 GMT</pubDate><content:encoded>&lt;p&gt;Django has some fields you can define in your models that are specific to a given database engine. The problem is, once your codebase starts using those fields, the project will never work again with another database engine because of the migrations that will fail on different engines.&lt;/p&gt;
&lt;p&gt;However, it’s hard to remind yourself not to use these fields, and coordinating that on a big project with multiple developers is close to impossible. Which is why you can write a Django system check that will disallow such fields from creating a migration file:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; inspect&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; django.contrib.postgres.fields&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; django.apps&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; django.core.checks &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Error, register&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# noinspection PyUnusedLocal&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;@register&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; disallow_postgres_specific_fields_check&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(app_configs, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;kwargs):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    errors &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    disallowed_fields &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        item[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; item &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; inspect.getmembers(django.contrib.postgres.fields, inspect.isclass)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; model &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; django.apps.apps.get_models():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; field &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; model._meta.get_fields():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; disallowed_field &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; disallowed_fields:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                # PyCharm bug, see: https://youtrack.jetbrains.com/issue/PY-32860&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                # noinspection PyTypeHints&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;                if&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; isinstance&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(field, disallowed_field):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                    errors.append(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                        Error(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;                            f&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Field &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; cannot be used as it is a Postgres-specific field: &quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;                            f&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;disallowed_field.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;__name__}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                            hint&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Use fields that are database engine-agnostic and provided by Django.&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                            id&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;config.E005&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Use an ID specific to your codebase&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                        )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                    )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; errors&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/shipperstack/shipper/commit/cfa70d11c365a026cbae8326a55d3d40a5c13eee&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Here’s the final commit in the shipper project&lt;/a&gt;, if you’re interested.&lt;/p&gt;</content:encoded></item><item><title>I need a new browser</title><link>https://ericswpark.com/blog/2024/2024-08-05-i-need-a-new-browser/</link><guid isPermaLink="true">https://ericswpark.com/blog/2024/2024-08-05-i-need-a-new-browser/</guid><pubDate>Mon, 05 Aug 2024 04:11:00 GMT</pubDate><content:encoded>&lt;p&gt;In May this year, I switched from Firefox to Chrome after the Mozilla team rejected one of my crappy browser extensions from their store:&lt;/p&gt;
&lt;iframe src=&quot;https://tilde.zone/@ericswpark/112396955326625035/embed&quot; class=&quot;mastodon-embed max-w-full border-0 mx-auto&quot; width=&quot;800&quot; allowfullscreen=&quot;allowfullscreen&quot;&gt;&lt;/iframe&gt; &lt;script src=&quot;https://tilde.zone/embed.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
&lt;p&gt;Okay, that wasn’t the entire reason. For years, I’ve been quite annoyed by the design decisions of the developers behind Firefox, with the arbitrary reasons behind not supporting latest web standards such as WebUSB:&lt;/p&gt;
&lt;iframe src=&quot;https://tilde.zone/@ericswpark/112792913950709534/embed&quot; class=&quot;mastodon-embed max-w-full border-0 mx-auto&quot; width=&quot;800&quot; allowfullscreen=&quot;allowfullscreen&quot;&gt;&lt;/iframe&gt; &lt;script src=&quot;https://tilde.zone/embed.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
&lt;p&gt;It doesn’t help that Mozilla touts Firefox as a privacy-respecting browser, but then turns around and &lt;a href=&quot;https://blog.privacyguides.org/2024/07/14/mozilla-disappoints-us-yet-again-2/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;implements crap like an ad-tracking API&lt;/a&gt;. And this isn’t even their first dumbest idea: &lt;a href=&quot;https://www.theverge.com/2017/12/16/16784628/mozilla-mr-robot-arg-plugin-firefox-looking-glass&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;they got caught doing some stupid stuff with a not-funny-at-all easter egg&lt;/a&gt; (I can only assume what drugs the marketing team were taking when they came up with that idea).&lt;/p&gt;
&lt;p&gt;So I switched. And life was good. For about two months. Then I noticed this in my Chrome settings:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/_astro/chrome-ublock-origin-unsupported.XLmLpqN2_Zj6h0J.webp&quot; alt=&quot;Chrome&apos;s extensions settings page. Under &amp;#34;This extension may soon no longer be supported&amp;#34;, uBlock Origin is listed&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;958&quot; height=&quot;212&quot;&gt;&lt;/p&gt;
&lt;p&gt;I now need to switch my main browser again.&lt;/p&gt;
&lt;h1 id=&quot;features-i-need&quot;&gt;Features I need&lt;/h1&gt;
&lt;h2 id=&quot;webusb--other-latest-web-apis&quot;&gt;WebUSB / other latest web APIs&lt;/h2&gt;
&lt;p&gt;I feel like this is the hardest feature to satisfy, so I’m not even going to try. Because right now, the only browser that implements WebUSB seems to be Chromium and co.&lt;/p&gt;
&lt;p&gt;Thankfully, not a whole lot of websites use such APIs (yet), so I can just keep a copy of Chromium around for such instances where I do need to interface with a device that uses them.&lt;/p&gt;
&lt;p&gt;Again, Mozilla developers, it would be great if this was &lt;strong&gt;natively supported&lt;/strong&gt; and I &lt;em&gt;didn’t&lt;/em&gt; have to install a “privacy-invading” browser as you guys claim to do something like reflash my Arduino boards. Unfortunately, the &lt;a href=&quot;https://mozilla.github.io/standards-positions/#webusb&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Mozilla Specifications Positions page makes their stance on WebUSB quite clear&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;(…) Because many USB devices are not designed to handle potentially-malicious interactions over the USB protocols and because those devices can have significant effects on the computer they’re connected to, we believe that the security risks of exposing USB devices to the Web are too broad to risk exposing users to them or to explain properly to end users to obtain meaningful informed consent. It also poses risks that sites could use USB device identity or data stored on USB devices as tracking identifiers.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Okay, then feature-flag it and chuck it in &lt;code&gt;about:config&lt;/code&gt; for people that &lt;em&gt;do&lt;/em&gt; understand how it works. You guys love sticking config options in there anyway. Why not one more?&lt;/p&gt;
&lt;p&gt;Honestly, if the permission dialog for WebUSB/WebBluetooth/etc. had a giant scary triangle with yellow or red colors that screamed “this is dangerous” then I think it’s deterrent enough for regular users to hit “Deny”.&lt;/p&gt;
&lt;h2 id=&quot;sync&quot;&gt;Sync&lt;/h2&gt;
&lt;p&gt;Chrome’s Sync is really, really good. I don’t think I had a history item that didn’t sync across devices.&lt;/p&gt;
&lt;p&gt;Contrast that with Firefox Sync, and you just know Mozilla’s implementation isn’t as good. Personally, I’ve had so many issues with syncing that it drives me crazy. Sending tabs to devices is a crapshoot and every now and then it doesn’t work.&lt;/p&gt;
&lt;p&gt;However, I can’t make a concession on this point, because I value being able to pick up my work right where I left off on my other devices. Loading up a long article on my laptop, and then riding the subway and reading that same article on my phone becomes much more cumbersome if I have to manually copy and paste URLs around.&lt;/p&gt;
&lt;p&gt;Which leads us right to…&lt;/p&gt;
&lt;h2 id=&quot;mobile-apps&quot;&gt;Mobile apps&lt;/h2&gt;
&lt;p&gt;I’m currently back on my iPhone after &lt;a href=&quot;/blog/2024/2024-07-08-i-got-scammed-on-swappa&quot;&gt;the disastrous attempt of switching to Android a couple of months ago&lt;/a&gt;, which means every browser is just reskinned WebKit. (&lt;a href=&quot;https://www.theverge.com/2024/1/25/24050478/apple-ios-17-4-browser-engines-eu&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Unless you’re in the EU, of course, because that’s where you get actual basic human rights, apparently.&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Again, Chrome makes the best of it with WebKit, but Firefox’s iOS app is quite buggy. I don’t blame the developers, but there are still outstanding bug reports that I filed a few years back that have still yet to be fixed. (Like &lt;a href=&quot;https://github.com/mozilla-mobile/firefox-ios/issues/14279&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;this&lt;/a&gt;, &lt;a href=&quot;https://github.com/mozilla-mobile/firefox-ios/issues/13973&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;this&lt;/a&gt;, and &lt;a href=&quot;https://github.com/mozilla-mobile/firefox-ios/issues/12051&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;this&lt;/a&gt;. And some that I didn’t report but personally encountered, such as &lt;a href=&quot;https://github.com/mozilla-mobile/firefox-ios/issues/11318&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;this one&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;Unfortunately, LibreWolf — the Firefox fork I was eyeing — do not have any mobile apps, and from what I can gather from their documentation, don’t have any immediate plans on creating any. Coupled with the previous Sync requirement, this rules out a vast majority of browsers that don’t have a big developer team behind it, at least for me and for the time being.&lt;/p&gt;
&lt;h2 id=&quot;ublock-origin&quot;&gt;uBlock Origin&lt;/h2&gt;
&lt;p&gt;This is another hard requirement of mine, and one which rules out nearly all Chromium-based browsers immediately. (Why else would I be switching from Chrome?)&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/brave/brave-browser/issues/20059#issuecomment-992720832&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Brave is a Chromium-based browser that claims they’ll retain Manifest V2&lt;/a&gt; after &lt;a href=&quot;https://killedbygoogle.com/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Google yanks and kills it&lt;/a&gt;, but this &lt;a href=&quot;https://x.com/BrendanEich/status/1534893414579249152&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;hinges on Google not removing the code paths from Chromium&lt;/a&gt; as referenced in the Brave CEO’s tweet (or Xeet or whatever they call it nowadays). They think it’s required for enterprise support, but I don’t want to switch browsers &lt;em&gt;yet again&lt;/em&gt; if their bet turns out to be a dud.&lt;/p&gt;
&lt;h1 id=&quot;switching-to&quot;&gt;Switching to…&lt;/h1&gt;
&lt;p&gt;So for the time being, it seems like I’ll grudgingly be going back to Firefox, and staying there until a better alternative pops up or until Mozilla manages to kill it for good.&lt;/p&gt;
&lt;p&gt;Just like a &lt;a href=&quot;/blog/2024/2024-06-28-davinci-resolve-short-codec-testing&quot;&gt;NLE video editor&lt;/a&gt;, it seems like good browsers are hard to come by.&lt;/p&gt;</content:encoded></item><item><title>I got scammed on Swappa</title><link>https://ericswpark.com/blog/2024/2024-07-08-i-got-scammed-on-swappa/</link><guid isPermaLink="true">https://ericswpark.com/blog/2024/2024-07-08-i-got-scammed-on-swappa/</guid><pubDate>Mon, 08 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I bought a brand-new, unopened Samsung Galaxy Z Fold5 back in January this year. At the time, I needed a phone to develop Android apps on, and I was browsing through Swappa when this listing caught my eye:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Original listing&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;978&quot; height=&quot;758&quot; src=&quot;/_astro/original-listing.Bb6bygwl_29usFW.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;At the time I thought it was a great deal, and the thought of being able to test apps on a foldable screen made me pick it up.&lt;/p&gt;
&lt;p&gt;I only had the rational thoughts after I’d paid for the listing, so I contacted the seller and asked if the phone was legitimate. This was the (rather shouty) response:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Response from the seller saying that the phone is not stolen&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;639&quot; height=&quot;171&quot; src=&quot;/_astro/not-stolen.DZ9vCblJ_ZhuLOS.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;The only thing I could do was wait and hope that the phone really was legitimate. And when it showed up, for a while I thought that it really was. It was sealed in the factory original packaging, with a pre-loaded Google Fi SIM card. I guessed the seller had gotten the phone from Fi as a promotion and was selling it on.&lt;/p&gt;
&lt;p&gt;And all was well, for about five months.&lt;/p&gt;
&lt;h1 id=&quot;the-problems-start&quot;&gt;The problems start&lt;/h1&gt;
&lt;p&gt;Then one sunny day in June, I noticed that my phone didn’t have reception. I was out on vacation in Korea, and noticed that the US eSIM that I had wasn’t getting any roaming signal. Thinking it was a simple bug with the system, I contacted Visible and asked them if they could re-provision the eSIM and enable Wi-Fi calling.&lt;/p&gt;
&lt;p&gt;After a bit of back and forth, this is what they finally responded with:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Visible email&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1928&quot; height=&quot;744&quot; src=&quot;/_astro/visible-email.Ctp4T7H-_Z10GawC.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;Sure enough, when I looked up the IMEI, it showed up as blacklisted:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;IMEI lookup&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;966&quot; height=&quot;809&quot; src=&quot;/_astro/imei-lookup.oKspDboF_Ze02qC.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;Of course, the seller stopped responding on Swappa, so I filed a PayPal claim. However, PayPal took so much time sorting out the claim that I thought they were trying to help out the seller at times. Despite being unresponsive on Swappa, the seller provided fishy “evidence” on the PayPal claim, saying ridiculous things like the phone not being blacklisted at the time of sale. (And?)&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Seller response on PayPal&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;511&quot; height=&quot;160&quot; src=&quot;/_astro/seller-response-paypal.Dvz4bVoZ_E3SMs.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;After a month of back-and-forth like this, PayPal told me that the only way I was getting a refund was if I submitted a police report:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;PayPal demands a police report&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;862&quot; height=&quot;679&quot; src=&quot;/_astro/paypal-case-timeline-and-police-report-request.CRSzC9qp_Z1TTEpC.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;The trouble was, I was in Korea, and I didn’t have reception because my phone had been blacklisted. To top it all off, Visible didn’t allow re-downloading the eSIM outside of the US.&lt;/p&gt;
&lt;p&gt;I first ported my phone number to a carrier that did allow eSIM downloads outside of the US, and also supported Wi-Fi calling activations overseas (I have no idea why this isn’t the standard). Then I called up the sheriff offices and asked for an officer that I could file a report with. I’d like to thank the officer at the Tippecanoe County Sheriff for helping me out — he even sent the report over the Fourth of July:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Police report&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1472&quot; height=&quot;512&quot; src=&quot;/_astro/police-report.6hq118kh_Z1qCWqj.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;Finally, after nearly a month since I first reported the claim to PayPal, PayPal closed the claim in my favor and refunded me the full amount.&lt;/p&gt;
&lt;h1 id=&quot;how&quot;&gt;How?&lt;/h1&gt;
&lt;p&gt;So how did my phone get blacklisted five months after the sale? And what can you do to avoid such a thing?&lt;/p&gt;
&lt;p&gt;Well, as part of gathering evidence to submit to PayPal, I contacted Google Fi about the blacklisted phone, and this is what they had to say:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Google Fi support response&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1582&quot; height=&quot;875&quot; src=&quot;/_astro/google-fi-support-response.DNqYwYE7_Z168oWV.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;So here’s how the scam works:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The scammer buys a new phone from Google Fi, backed by insurance&lt;/li&gt;
&lt;li&gt;The scammer sells the phone on used marketplaces like Facebook Marketplace and Swappa&lt;/li&gt;
&lt;li&gt;After the sale, the scammer gives some time to make the buyer think the phone is legitimate&lt;/li&gt;
&lt;li&gt;The scammer then claims the phone as lost/stolen&lt;/li&gt;
&lt;li&gt;Insurance gives the scammer a new phone to repeat the scam with&lt;/li&gt;
&lt;li&gt;Insurance blacklists the original phone, screwing over the buyer&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is a pretty clever tactic, because if you didn’t use something like PayPal and don’t have buyer’s protection, you’re absolutely screwed at this point unless you take the scammer to a small claims court or something. I was very nearly in this position because my buyer’s protection on PayPal expired a week after the claim was closed. If the scammer had been a little bit more patient then they totally would’ve gotten away with it.&lt;/p&gt;
&lt;p&gt;And for the scammer, there’s literally no downsides. If they get away with it, they make nearly a grand and still get their phones for their next scam. If they don’t (like my case), they only lose out on the UPS shipping fees and the seller chargeback fees (if PayPal charges any). Which is probably why they did this with other folks, too:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Review from scammed buyer&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;2159&quot; height=&quot;765&quot; src=&quot;/_astro/review-1.BaOUw4Wq_23r9P7.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Another review from scammed buyer&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;997&quot; height=&quot;488&quot; src=&quot;/_astro/review-2.B9UyH_S2_Z1Xes8p.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;Unfortunately, there’s no real good way of protecting yourself from scams like this, other than not buying used phones. I’m personally never buying a used phone again after this incident unless the seller is someone I trust. Phone insurance can go as long as the original owner keeps paying for it, and the used phone can be blacklisted at any point.&lt;/p&gt;
&lt;p&gt;The massive headache I had to go through to get my US cell service back up while overseas was truly the worst experience that I’d never like to go through again. Don’t do what I did and pay a little more for peace of mind.&lt;/p&gt;
&lt;h1 id=&quot;update&quot;&gt;Update&lt;/h1&gt;
&lt;p&gt;PayPal banned me:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;PayPal ban notice&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;572&quot; height=&quot;520&quot; src=&quot;/_astro/paypal-ban-notice.ByZUIFXj_2cqoua.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;They initially told me that this was because I was accessing my US PayPal account from another country (yeah, I was traveling. So what?) but then refused to elaborate more when I explained I was out on vacation. I’m guessing the ban reason is because of the claim opened months after the sale.&lt;/p&gt;
&lt;p&gt;I called them up and asked about an appeal, but they told me that it was denied and the decision was final. Very classy, PayPal.&lt;/p&gt;</content:encoded></item><item><title>DaVinci Resolve short codec testing</title><link>https://ericswpark.com/blog/2024/2024-06-28-davinci-resolve-short-codec-testing/</link><guid isPermaLink="true">https://ericswpark.com/blog/2024/2024-06-28-davinci-resolve-short-codec-testing/</guid><pubDate>Fri, 28 Jun 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Update: the following only applies to the free version of DaVinci Resolve. I’ve since learned that the Studio version (with the license) supports up to 10-bit color space, and I’ve also confirmed that it can import and play back the codecs below without any issues. For posterity (and for free users) I’ll leave this blog post here, but I strongly recommend purchasing a copy of the Studio license if you need to frequently edit 10-bit media.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I spent some time today testing out the different video format settings on my camera with DaVinci Resolve, as I got burned on the latest project I was working on when none of the footage I shot on my ZV-E1 would import correctly into DVR. The video files would import as audio clips with no apparent warnings or errors.&lt;/p&gt;
&lt;p&gt;Here are the test results, as of DaVinci Resolve 18.6:&lt;/p&gt;





















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;File format (fps)&lt;/th&gt;&lt;th&gt;Bitrate / Color space&lt;/th&gt;&lt;th&gt;Imported?&lt;/th&gt;&lt;th&gt;Notes&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;XAVC HS 4K (24 fps)&lt;/td&gt;&lt;td&gt;100M / 4:2:2 / 10 bit&lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;td&gt;Imported as audio clip&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;XAVC HS 4K (24 fps)&lt;/td&gt;&lt;td&gt;100M / 4:2:0 / 10 bit&lt;/td&gt;&lt;td&gt;Yes*&lt;/td&gt;&lt;td&gt;First few seconds corrupt&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;XAVC HS 4K (24 fps)&lt;/td&gt;&lt;td&gt;50M / 4:2:2 / 10 bit&lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;td&gt;Imported as audio clip&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;XAVC HS 4K (24 fps)&lt;/td&gt;&lt;td&gt;50M / 4:2:0 / 10 bit&lt;/td&gt;&lt;td&gt;Yes*&lt;/td&gt;&lt;td&gt;First few seconds corrupt&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;XAVC S 4K (30 fps)&lt;/td&gt;&lt;td&gt;140M / 4:2:2 / 10 bit&lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;td&gt;Imported as audio clip&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;XAVC S 4K (30 fps)&lt;/td&gt;&lt;td&gt;100M / 4:2:0 / 8 bit&lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;XAVC S 4K (30 fps)&lt;/td&gt;&lt;td&gt;60M / 4:2:0 / 8 bit&lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Some notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I was unable to test any of the XAVC S-I options, as none of the SD or microSD cards I have on hand are fast enough (V90) :/&lt;/li&gt;
&lt;li&gt;For the entries that imported with an asterisk, something interesting happened in the first couple of seconds of the footage where the video frames showed up with this weird corruption/color-banding effect in DVR:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt=&quot;corrupt-frames&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1261&quot; height=&quot;796&quot; src=&quot;/_astro/corrupt-frames.CF-eL5NG_Z1DXRpH.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;I initially thought that maybe the microSD card was going bad, so I swapped it with a spare and tested again, but the same exact problem occurred. I then ran the video through MPC-HC and discovered that the corruption issue was not there.&lt;/p&gt;
&lt;h1 id=&quot;summary&quot;&gt;Summary&lt;/h1&gt;
&lt;p&gt;As of DaVinci Resolve 18.6, on the free version, the following setting on your camera (assuming you use a Sony ZV-E1) is your best bet:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;XAVC S 4K, 100M / 4:2:0 / 8 bit&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you use the 4:2:2 chroma subsampling, your videos will import as audio clips.&lt;/li&gt;
&lt;li&gt;If you use 10 bit, the first few seconds of the footage will be corrupt, and there may be other rendering issues exporting the video.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And I’m still looking for a good NLE video editor that won’t make me sign away my newborn (cough Premiere cough) and won’t crash every ten seconds (Kdenlive) and doesn’t have codec allergies. Tag me on Mastodon if you have any suggestions!&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;what-color-do-you-want-your-nle-editor-meme&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;489&quot; height=&quot;492&quot; src=&quot;/_astro/meme.C3mmG6RO_NXyVc.webp&quot; &gt;&lt;/p&gt;</content:encoded></item><item><title>Linux on daily desktop attempt lasted two days</title><link>https://ericswpark.com/blog/2024/2024-06-06-linux-on-daily-desktop-attempt-lasted-two-days/</link><guid isPermaLink="true">https://ericswpark.com/blog/2024/2024-06-06-linux-on-daily-desktop-attempt-lasted-two-days/</guid><pubDate>Thu, 06 Jun 2024 06:43:38 GMT</pubDate><content:encoded>&lt;p&gt;A quick summary of what went wrong:&lt;/p&gt;
&lt;h1 id=&quot;nvidia-graphics&quot;&gt;Nvidia graphics&lt;/h1&gt;
&lt;p&gt;I know it’s &lt;em&gt;always&lt;/em&gt; Nvidia graphics, but quite honestly it got very close this time! The only problem is, I kept getting those weird flickering issues on KDE + Wayland:&lt;/p&gt;
&lt;iframe src=&quot;https://tilde.zone/@ericswpark/112564801545814325/embed&quot; class=&quot;mastodon-embed max-w-full border-0 mx-auto&quot; width=&quot;800&quot; allowfullscreen=&quot;allowfullscreen&quot;&gt;&lt;/iframe&gt; &lt;script src=&quot;https://tilde.zone/embed.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
&lt;p&gt;From reading online, it seems to be a problem with the Nvidia drivers not supporting explicit sync on Wayland. Support was added in driver version 555, but it was still in beta and the stable one was stuck at 550. (And while this was going on, Windows had the 555.x drivers for quite some time now…)&lt;/p&gt;
&lt;p&gt;As a workaround, I switched to using X11, which solved this bug but made the experience much slower.&lt;/p&gt;
&lt;h1 id=&quot;input-method&quot;&gt;Input method&lt;/h1&gt;
&lt;p&gt;I just tried to get Korean input working with nimf, but I guess I made a mistake writing &lt;code&gt;.xprofile&lt;/code&gt;, because the entire desktop environment ended up going away, never to return:&lt;/p&gt;
&lt;iframe src=&quot;https://tilde.zone/@ericswpark/112566980149788228/embed&quot; class=&quot;mastodon-embed max-w-full border-0 mx-auto&quot; width=&quot;800&quot; allowfullscreen=&quot;allowfullscreen&quot;&gt;&lt;/iframe&gt; &lt;script src=&quot;https://tilde.zone/embed.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
&lt;p&gt;Sidenote: it’s still a pretty terrible experience configuring an input engine on Linux. I chose &lt;code&gt;nimf&lt;/code&gt; because it worked quite well on my other Linux machines using Wayland, but the procedure could use some polish. The current installation procedure involves adding &lt;a href=&quot;https://wiki.archlinux.org/title/Nimf#Initial_setup&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;the lines as described on the Arch Linux wiki to &lt;code&gt;.xprofile&lt;/code&gt;&lt;/a&gt;, signing out and signing back in, and then rebooting when you realize that didn’t work, the messing about with the KDE keyboard settings a little, then finally somehow getting it working after invoking &lt;code&gt;nimf &amp;amp;&lt;/code&gt; a bunch of times.&lt;/p&gt;
&lt;p&gt;Instead, this should be a single configurable option in the desktop environment’s respective settings page, and hopefully not require signing out or rebooting.&lt;/p&gt;
&lt;p&gt;Anyway, that concludes my two day testing of Linux on my daily-use desktop. I’ll probably give it another shot once the 555 driver is out.&lt;/p&gt;</content:encoded></item><item><title>Passkey fail</title><link>https://ericswpark.com/blog/2024/2024-04-26-passkey-fail/</link><guid isPermaLink="true">https://ericswpark.com/blog/2024/2024-04-26-passkey-fail/</guid><pubDate>Fri, 26 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://fy.blackhats.net.au/blog/2024-04-26-passkeys-a-shattered-dream/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;William Brown&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;However Chrome simply never implemented it leading to it being removed. And it was removed because Chrome never implemented it. As a result, if Chrome doesn’t like something in the specification they can just veto it without consequence.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I hate waiting on browser support. About a year or two ago, I transitioned my family to use hardware keys to sign in to services, and it was truly fucking awful. Back when I started using hardware keys, Safari on iOS had zero support for FIDO keys (so before iOS 13 or 14?) and when they finally added it they only added support for NFC keys or something like that, which meant keys that relied on Bluetooth and USB connections wouldn’t work at all, unless you got Google’s helper app and did some weird pairing song-and-dance.&lt;/p&gt;
&lt;p&gt;It feels like that, but all over again for Passkeys. Bitwarden supports it, but only on desktop browser extensions. (Did I mention that none of the client apps, aside from the browser extension, support hardware keys in 2024? Forcing you to enroll another 2FA just to sign in to those apps, that weakens overall account security?) If I can’t store it in a password manager, I can’t get cross-device sync, and I &lt;strong&gt;do not&lt;/strong&gt; want to dig out a single device every time I want to authenticate to a given service.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Both Chrome and Safari will try to force you into using either hybrid (caBLE) where you scan a QR code with your phone to authenticate - you have to click through menus to use a security key. caBLE is not even a good experience, taking more than 60 seconds work in most cases. The UI is beyond obnoxious at this point. Sometimes I think the password game has a better ux.&lt;/p&gt;
&lt;p&gt;The more egregious offender is Android, which won’t even activate your security key if the website sends the set of options that are needed for Passkeys. This means the IDP gets to choose what device you enroll without your input. And of course, all the developer examples only show you the options to activate “Google Passkeys stored in Google Password Manager”. After all, why would you want to use anything else?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Yeah, why wouldn’t I want to store my passkey in my iCloud account? Oh, because I actually own devices that aren’t made in Cupertino and aren’t allowed to talk to Apple’s servers. And on Android it’s Google trying to hoover up the passkeys.&lt;/p&gt;
&lt;p&gt;At this point, passkeys are just SSO with extra steps.&lt;/p&gt;</content:encoded></item><item><title>Korea&apos;s mobile carriers are evolving backwards</title><link>https://ericswpark.com/blog/2024/2024-04-06-koreas-mobile-carriers-are-evolving-backwards/</link><guid isPermaLink="true">https://ericswpark.com/blog/2024/2024-04-06-koreas-mobile-carriers-are-evolving-backwards/</guid><pubDate>Sat, 06 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As I’ve lived in the US for the last year or so, I feel qualified in comparing cell service between South Korea and the US, and describing how shit the former is.&lt;/p&gt;
&lt;h1 id=&quot;no-mixing-numbers&quot;&gt;No mixing numbers&lt;/h1&gt;
&lt;p&gt;Korea was pretty late to the eSIM game, but when we finally got eSIM, it came with a whole bunch of restrictions. The biggest restriction is that &lt;strong&gt;your phone lines get suspended if you happen to mix SIMs with different identities on a single handset.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Your mom’s phone stopped working, and you want to see if it’s a phone problem or a USIM problem so you put your mom’s USIM in your phone? Too bad — now both you and your mother’s cell service have been suspended, which means you have to go to the nearest carrier store to beg them to reactivate it.&lt;/p&gt;
&lt;p&gt;(Note that this restriction only became an issue because we didn’t really have dual-SIM phones in Korea for a very long time, probably because the Korean carriers lobbied hard against it. But with the introduction of eSIM, mixing SIM cards in the manner described above became possible.)&lt;/p&gt;
&lt;p&gt;Of course, the Korean carriers claim this is to prevent the proliferation of stolen burner phones, but we had that issue back when we had physical USIMs, so I’m not sure what effect this restriction will have in preventing such problems.&lt;/p&gt;
&lt;h1 id=&quot;imei-collection&quot;&gt;IMEI collection&lt;/h1&gt;
&lt;p&gt;To facilitate the above restriction, Korean carriers collect both IMEI values of your phone when you sign up for an eSIM. They use this information to verify that a given handset only has SIMs tied to one identity.&lt;/p&gt;
&lt;p&gt;This is ridiculous. No other carrier requires you to submit both IMEI values just to activate an eSIM card. When I activated my eSIM in the US, Visible (an MVNO of Verizon) asked me for one of the IMEIs on my phone, and that was it.&lt;/p&gt;
&lt;p&gt;Speaking of activating eSIMs…&lt;/p&gt;
&lt;h1 id=&quot;activating-esims-in-korea-sucks&quot;&gt;Activating eSIMs in Korea sucks&lt;/h1&gt;
&lt;p&gt;Korean carriers charge you 2,750 won (about $2) every time you re-download your eSIM. In stark contrast, I can move my eSIM around with my US carrier Visible with zero restrictions. In fact, I can’t think of a single carrier around the world, except for Korea, that charges for an eSIM transfer.&lt;/p&gt;
&lt;p&gt;The entire point of eSIM is that since it’s all digital, there’s no physical card to make. The cost of moving bits around on a server and beaming me that eSIM info should be negligible.&lt;/p&gt;
&lt;p&gt;Now, apparently the Korean carriers are claiming the cost is due to royalties involved in implementing eSIM. But considering other carriers around the world don’t pass that on to their subscribers, and considering we’re already paying a whole lot of money just for cell service, I think they should stop being so freaking greedy and eat the cost.&lt;/p&gt;
&lt;p&gt;Oh, and actually re-downloading the eSIM?&lt;/p&gt;
&lt;p&gt;In the US, re-downloading an eSIM is just a couple of taps on the mobile app.&lt;/p&gt;
&lt;p&gt;In Korea, you need to call their hotline during working hours and &lt;em&gt;hope&lt;/em&gt; that the process works, or go to a physical carrier store. Kind of hard to do when you’re on the other side of the freaking globe, like me.&lt;/p&gt;
&lt;h1 id=&quot;the-arbitrary-4g5g-divide&quot;&gt;The arbitrary 4G/5G divide&lt;/h1&gt;
&lt;p&gt;In America (and many other parts of the world), there is no distinction between the different generations of cell service. If you bought a new 5G phone, pulled your SIM from your old 4G phone and put it in the new one, then you’d get 5G service, automatically.&lt;/p&gt;
&lt;p&gt;In Korea, you have to sign up for a specific 4G &lt;em&gt;or&lt;/em&gt; 5G plan when you sign up. Of course you have to pay more to get 5G service.&lt;/p&gt;
&lt;p&gt;This creates a bit of a problem, where people are content with just using their 4G services, because 5G is marginally better than 4G due to the infrastructure just getting started. Then carriers don’t want to spend money on expanding the infrastructure, which leads to stagnation and 4G and 5G being used in tandem.&lt;/p&gt;
&lt;p&gt;But in other countries, all users automatically get 5G service, so they have more reasons to expand and set up more 5G cell towers. This also allows them to deprecate old gear and start removing 3G and 4G services.&lt;/p&gt;
&lt;p&gt;In the long term, focusing on maximizing profits by putting arbitrary distinctions on cell service generations will lead to Korea falling behind in terms of cell service quality, compared to other countries.&lt;/p&gt;
&lt;p&gt;Speaking of quality going downhill, can I introduce a cell service feature that is nonexistent in Korea?&lt;/p&gt;
&lt;h1 id=&quot;wi-fi-calling-or-lack-thereof&quot;&gt;Wi-Fi calling (or lack thereof)&lt;/h1&gt;
&lt;p&gt;It’s quite ironic: there are tons and tons of Wi-Fi access points in South Korea, but if you were to try and call through them when you’re in an area with Wi-Fi coverage but no cell coverage, you’ll discover that you can’t.&lt;/p&gt;
&lt;p&gt;I got bit by this once when I was traveling in the countryside and got to a place with zero cell service, but pretty decent Wi-Fi. I still couldn’t make any calls or send any texts until I walked closer toward an urban area.&lt;/p&gt;
&lt;p&gt;Most first-world country cell carriers already implemented this feature ages ago, so I have no idea what would drive Korean carriers to not implement this, other than cutting corners.&lt;/p&gt;
&lt;p&gt;Since we’re talking about costs, I’ll wrap up with…&lt;/p&gt;
&lt;h1 id=&quot;the-cost-of-unlimited&quot;&gt;The cost of unlimited&lt;/h1&gt;
&lt;p&gt;In the US, I pay $25 for unlimited calls, texts, and data, on 5G. Even data for tethering is free, no strings attached.&lt;/p&gt;
&lt;p&gt;In Korea, I need to pay over 100,000 won (about $74) just to get close. And even if I pay 3 times as much, I still get data limits tethering.&lt;/p&gt;
&lt;p&gt;If nothing changes, I fully expect this trend to continue. We will get less for more.&lt;/p&gt;</content:encoded></item><item><title>I hate Lockdown Browser</title><link>https://ericswpark.com/blog/2024/2024-02-20-i-hate-lockdown-browser/</link><guid isPermaLink="true">https://ericswpark.com/blog/2024/2024-02-20-i-hate-lockdown-browser/</guid><pubDate>Tue, 20 Feb 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Recently I’ve been working off of my Linux laptop for most things, because having a terminal around that isn’t running Windows or macOS is fun and useful.&lt;/p&gt;
&lt;p&gt;That got me wondering if I could take one of my courses’ exams on the machine, since it used Respondus’s Lockdown Browser, an exam-focused browser that locks you out of all other apps for the duration of the exam.&lt;/p&gt;
&lt;p&gt;Long story short: you can’t. But if you want to follow along, then you can see how terrible Lockdown Browser is, and the insanity that students have to deal with nowadays.&lt;/p&gt;
&lt;h1 id=&quot;is-there-a-linux-client&quot;&gt;Is there a Linux client?&lt;/h1&gt;
&lt;p&gt;First thing I checked. No.&lt;/p&gt;
&lt;h1 id=&quot;does-it-run-under-a-vm&quot;&gt;Does it run under a VM?&lt;/h1&gt;
&lt;p&gt;Yes, it runs. It shows you this error message:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;lockdown-browser-lockout-part-1&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1563&quot; height=&quot;1016&quot; src=&quot;/_astro/lockdown-browser-lockout-part-1.ocOgNpri_Z27dX81.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;And quits.&lt;/p&gt;
&lt;p&gt;But we’re Linux nerds, so we’ll dig a little deeper.&lt;/p&gt;
&lt;h1 id=&quot;tricking-lockdown-browser&quot;&gt;Tricking Lockdown Browser&lt;/h1&gt;
&lt;p&gt;We need to hide the fact that we’re running LDB inside a VM, from LDB.&lt;/p&gt;
&lt;p&gt;First order of business is to pass through the SMBIOS information from the host machine to the virtual machine, by editing QEMU’s XML. Add the following lines between the &lt;code&gt;&amp;#x3C;os&gt; &amp;#x3C;/os&gt;&lt;/code&gt; XML tags:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x3C;os&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &amp;#x3C;!-- Some other lines... --&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &amp;#x3C;smbios mode=&quot;host&quot; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x3C;/os&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, we need to disguise the CPU. By default, QEMU reports to the virtual machine that it is being virtualized, and that the vCPU is not a physical CPU. We need to tell QEMU to not be so honest, and to pass in the CPU information directly. Find the &lt;code&gt;&amp;#x3C;cpu&gt;&lt;/code&gt; tag, and if it is enclosed, create a closing tag and paste in the following:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x3C;cpu ...&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;topology sockets=&quot;1&quot; dies=&quot;1&quot; cores=&quot;1&quot; threads=&quot;2&quot;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;feature policy=&quot;disable&quot; name=&quot;hypervisor&quot;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x3C;/cpu&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You will need to edit the CPU topology to match your vCPU configuration, or else it will either not apply, or your VM will crash.&lt;/p&gt;
&lt;p&gt;By disabling the hypervisor, we’re lying to the VM and telling it that it is not actually virtualized. The VM will now think the CPU has virtualization capabilities, not that it is being virtualized. Check using Task Manager inside the guest OS — if you see something like “Virtual Machine: yes”, then it didn’t work properly. It should say something like “Virtualization: Enabled”.&lt;/p&gt;
&lt;p&gt;Finally, the last thing we have to do is to hide device names from LDB. Boot into the virtual machine, fire up &lt;code&gt;regedit&lt;/code&gt;, and navigate to the following path:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Enum\SCSI&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Find each virtual drive device, and change the &lt;code&gt;FriendlyName&lt;/code&gt; to something like &lt;code&gt;Samsung HDD 500 GB ATA&lt;/code&gt; or &lt;code&gt;LG SuperDrive 20x CD-ROM&lt;/code&gt;. (You may need to grant yourself permissions if &lt;code&gt;regedit&lt;/code&gt; complains.)&lt;/p&gt;
&lt;p&gt;Once you’ve done all that, LDB should now launch.&lt;/p&gt;
&lt;p&gt;Very briefly.&lt;/p&gt;
&lt;h1 id=&quot;theres-a-second-lockout&quot;&gt;There’s a second lockout?&lt;/h1&gt;
&lt;p&gt;Unfortunately, LDB has a second lockout to lull you into a false sense of security. Once you start the exam, after a couple of seconds, you will see this screen:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;lockdown-browser-lockout-part-2&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1553&quot; height=&quot;1002&quot; src=&quot;/_astro/lockdown-browser-lockout-part-2.CO2gTHqt_Job8w.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;This probably uses some other VM detection technique. If you’re curious, there’s &lt;a href=&quot;https://github.com/a0rtega/pafish&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;a repository named pafish&lt;/a&gt; that goes over some of them.&lt;/p&gt;
&lt;p&gt;I could probably also find a workaround for this given enough time. But the exam is next week, and I really can’t be bothered, and if this happens on the actual exam I don’t want to get suspected of cheating when I only wanted to take the test on my Linux laptop. I can just take it on another computer.&lt;/p&gt;
&lt;p&gt;And therein lies the problem.&lt;/p&gt;
&lt;h1 id=&quot;what-is-lockdown-browser-trying-to-prevent-again&quot;&gt;What is Lockdown Browser trying to prevent again?&lt;/h1&gt;
&lt;p&gt;Lockdown Browser tries to prevent cheating by locking you out of the computer you’re taking the test on and disallowing you from opening other applications, like browsers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Only&lt;/strong&gt;, of course, on the computer you’re taking the test on.&lt;/p&gt;
&lt;p&gt;So what prevents a student from… using two computers? Or heck, a phone? Everybody has a phone right? &lt;a href=&quot;https://youtu.be/VGByCvWDINA?t=20&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;&lt;del&gt;Do you guys not have phones?&lt;/del&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Our professor actually recommended students to use one of the campus computers if we used our personal computers to take notes on, since the exam was (and the upcoming exams will be) open notes. What’s the point of using Lockdown Browser, then?&lt;/p&gt;
&lt;h1 id=&quot;and-some-reasons-why-you-should-avoid-installing-lockdown-browser&quot;&gt;And some reasons why you should avoid installing Lockdown Browser…&lt;/h1&gt;
&lt;p&gt;While testing out the VM techniques, I noticed that Lockdown Browser was causing the virtualized OS to act erratically. At one point, Task Manager refused to run, despite not having Lockdown Browser open. I had to restore a snapshot to get everything going again.&lt;/p&gt;
&lt;p&gt;That’s not all. If you search &lt;a href=&quot;https://www.google.com/search?q=lockdown+browser+broke+my+computer+site%3Areddit.com&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;“Lockdown Browser broke my computer”&lt;/a&gt;, you will see a ton of testimonials:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.reddit.com/r/KSU/comments/sq1qrr/lockdown_browser_crashed_my_dell_desktop/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://www.reddit.com/r/KSU/comments/sq1qrr/lockdown_browser_crashed_my_dell_desktop/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.reddit.com/r/jmu/comments/zqysg0/does_lockdown_browser_damage_anyone_elses_computer/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://www.reddit.com/r/jmu/comments/zqysg0/does_lockdown_browser_damage_anyone_elses_computer/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.reddit.com/r/utarlington/comments/jx6teg/respondus_lockdown_browser_keeps_messing_up_my/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://www.reddit.com/r/utarlington/comments/jx6teg/respondus_lockdown_browser_keeps_messing_up_my/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Which is not surprising. Lockdown Browser messes with Windows’s Group Policy, the Registry, and Power Options, to prevent other applications from opening, take full control of your computer, and prevent it from going to sleep. And sometimes the shoddy code of LDB means that your computer might not be the same state as before taking the exam, after the exam is over.&lt;/p&gt;
&lt;p&gt;And what better things do students have to do, than reinstall their operating system on their laptop and set up literally every single program that they need for their coursework, in the middle of the semester, and then do it all over again during finals week? Nothing, that’s what. I’m sure everyone cheers at the Windows/macOS setup wizard screen.&lt;/p&gt;
&lt;h1 id=&quot;any-alternatives&quot;&gt;Any alternatives?&lt;/h1&gt;
&lt;p&gt;There is &lt;a href=&quot;https://github.com/gucci-on-fleek/lockdown-browser/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;another project that utilizes Windows Sandbox&lt;/a&gt;, that actually works. (I’m guessing this is because Windows Sandbox utilizes Hyper-V, which is significantly harder to detect as it’s in Microsoft’s best interests to make it rock-solid for enterprise users and for preventing Xbox exploits.) However, &lt;a href=&quot;https://github.com/gucci-on-fleek/lockdown-browser/discussions/53&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;the project was recently placed into Respondus’s bug tracker&lt;/a&gt;, suggesting that this will be patched out soon.&lt;/p&gt;
&lt;h1 id=&quot;so-what-now&quot;&gt;So what now?&lt;/h1&gt;
&lt;p&gt;Once this blog post goes up, I’ll be emailing my professor a link so that hopefully the use of Lockdown Browser is reconsidered. If your school or college also utilizes Lockdown Browser, I suggest linking your instructor this blog post to hopefully convince them &lt;em&gt;not&lt;/em&gt; to use Lockdown Browser.&lt;/p&gt;
&lt;p&gt;Some might think that LDB is working because I was unable to get the VM detection evasion working. No, it’s not. My whole point is that LDB is unnecessary and just makes students lives more complicated because it messes up your computer for no good reason, while cheaters can just cheat away using other devices they have at their disposal while never ever triggering LDB’s mechanisms — because it is &lt;em&gt;impossible&lt;/em&gt; to detect that someone is using another device to cheat. Simply put, LDB punishes students, especially poorer ones that cannot afford multiple computers, while letting cheaters get away scot-free. (I think there might be some social commentary quip that’s appropriate but it’s midnight as I write this and I’m tired so please feel free to come up with one with your own imagination.)&lt;/p&gt;
&lt;p&gt;In the meantime, I guess I’ll go find a library computer or something to install this malware-esque examination program on. Because hell if I’m installing this on my personal computer, even if it’s possible on Linux in the first place.&lt;/p&gt;
&lt;h1 id=&quot;update&quot;&gt;Update&lt;/h1&gt;
&lt;p&gt;I actually got a reply from my professor today, and it seems like the Lockdown Browser requirement will be removed going forward! (Thank you professor if you’re reading this!)&lt;/p&gt;
&lt;p&gt;If your institution requires the use of Lockdown Browser, consider sending them this blog post to show them how ineffective it is. And if they change their mind, post about it somewhere! The more we speak out against this piece of shit software the more normalized it would be to &lt;em&gt;not&lt;/em&gt; use it.&lt;/p&gt;</content:encoded></item><item><title>PowerShell Incognito Mode</title><link>https://ericswpark.com/blog/2024/2024-02-03-powershell-incognito-mode/</link><guid isPermaLink="true">https://ericswpark.com/blog/2024/2024-02-03-powershell-incognito-mode/</guid><pubDate>Sat, 03 Feb 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;/blog/2021/2021-11-13-terminal-incognito-mode&quot;&gt;Back in 2021 (three years ago?!), I wrote a blog post about “incognito” mode in the bash terminal&lt;/a&gt;. Well, I also use Windows systems nowadays, and I needed a way of disabling PowerShell’s history function temporarily.&lt;/p&gt;
&lt;p&gt;PowerShell has two history modules. The first one is constrained within the current session, and you can see all the commands you ran during that session by running &lt;code&gt;Get-History&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;powershell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;PS C:\Users\erics\OneDrive\Pictures&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; Get-History&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  Id CommandLine&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  --&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; -----------&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;   1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; cd .\OneDrive\Pictures\&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;   2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ls&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The other history module is saved by PSReadLine, and this one is a bit more troublesome, as it functions exactly like &lt;code&gt;.bash_history&lt;/code&gt; — it saves your commands to a file that persists (until you get rid of Windows or delete that file). The path at which it’s saved is &lt;code&gt;$env:APPDATA\Microsoft\Windows\PowerShell\PSReadLine&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now, by default, the situation is even worse over on PowerShell, because commands prefixed with a space is still saved to the history, unlike bash.&lt;/p&gt;
&lt;p&gt;Thankfully, we can fix some of these annoyances ourselves by editing our profile script.&lt;/p&gt;
&lt;p&gt;(Note: most of the code below came from &lt;a href=&quot;https://github.com/PowerShell/PSReadLine/issues/2698&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;this GitHub issue discussing this exact problem&lt;/a&gt;!)&lt;/p&gt;
&lt;h2 id=&quot;editing-the-execution-policy&quot;&gt;Editing the execution policy&lt;/h2&gt;
&lt;p&gt;Because PowerShell is configured to not run any untrusted scripts by default, we need to adjust the execution policy:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;powershell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Set-ExecutionPolicy&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Scope CurrentUser &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ExecutionPolicy Unrestricted &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Force&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are two policies: &lt;code&gt;Unrestricted&lt;/code&gt; and &lt;code&gt;Bypass&lt;/code&gt;. &lt;code&gt;Unrestricted&lt;/code&gt; will at least warn you before executing scripts, while &lt;code&gt;Bypass&lt;/code&gt; will happily run anything and everything with zero warning (sort of like what &lt;code&gt;bash&lt;/code&gt; and Linux shells do by default with anything with the &lt;code&gt;+x&lt;/code&gt; flag.)&lt;/p&gt;
&lt;h2 id=&quot;edit-the-profile-script&quot;&gt;Edit the profile script&lt;/h2&gt;
&lt;p&gt;Open up the profile script in your favorite editor:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;powershell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;notepad&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; $profile&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And paste in the following:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;powershell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Don&apos;t record space-prefixed commands&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Set-PSReadLineOption&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;AddToHistoryHandler {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    param&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;([&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]$line)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; $line.Length &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-gt&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 3&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; -and&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; $line[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-ne&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos; &apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; -and&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; $line[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-ne&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;;&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Disable/enable history wrappers&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Disable-PSReadLineHistory&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    $&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;global&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:PSReadLineOldHistorySaveStyle &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Get-PSReadLineOption&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;).HistorySaveStyle&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    Set-PSReadLineOption&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;HistorySaveStyle SaveNothing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Enable-PSReadLineHistory&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # Only change the history style if we previously saved it in `Disable-PSReadLineHistory`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Test-Path&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; variable:PSReadLineOldHistorySaveStyle) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        Set-PSReadLineOption&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;HistorySaveStyle $&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;global&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:PSReadLineOldHistorySaveStyle&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, whenever you want to disable the PowerShell history temporarily, run &lt;code&gt;Disable-PSReadLineHistory&lt;/code&gt;. It will disable history recording for that current session.&lt;/p&gt;
&lt;p&gt;I wish they’d just add these in as the default.&lt;/p&gt;
&lt;h2 id=&quot;clearing-old-history&quot;&gt;Clearing old history&lt;/h2&gt;
&lt;p&gt;If you have sensitive commands recorded into history, then you can delete them by running &lt;code&gt;Clear-History&lt;/code&gt; and deleting the aforementioned PSReadLine’s history file.&lt;/p&gt;
&lt;p&gt;Or, you can use &lt;a href=&quot;https://stackoverflow.com/a/38807689/5202174&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;this script from Stack Overflow&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title>Windows Bluetooth shortcut with AutoHotkey</title><link>https://ericswpark.com/blog/2024/2024-01-07-windows-bluetooth-shortcut-with-autohotkey/</link><guid isPermaLink="true">https://ericswpark.com/blog/2024/2024-01-07-windows-bluetooth-shortcut-with-autohotkey/</guid><pubDate>Sun, 07 Jan 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Here’s a AutoHotKey script that quickly opens up the Bluetooth settings for Windows 11:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Persistent&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;#b::&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ; Open Quick Actions window&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    Send(&amp;quot;{LWin Down}a{LWin Up}&amp;quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ; The quick action window takes a while to initialize and allow keyboard navigation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ; Without the sleep timer you may end up on the wrong screen (or disabling WiFi)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ; If your computer is slower, increase this value a bit more&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    Sleep(600)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ; WiFi -&amp;gt; Bluetooth&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    Send(&amp;quot;{Right}&amp;quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ; Bluetooth chevron&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    Send(&amp;quot;{Tab}&amp;quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ; Enter Bluetooth chevron&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    Send(&amp;quot;{Enter}&amp;quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://www.theverge.com/2022/2/25/22951225/microsoft-windows-11-bluetooth-quick-settings-insider-testing-build&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Note that you need at least Build 22563 of Windows 11 in order to use this script.&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>I got SKT to make me a data-sharing eSIM!</title><link>https://ericswpark.com/blog/2023/2023-12-28-i-got-skt-to-make-me-a-data-sharing-esim/</link><guid isPermaLink="true">https://ericswpark.com/blog/2023/2023-12-28-i-got-skt-to-make-me-a-data-sharing-esim/</guid><pubDate>Thu, 28 Dec 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;By policy, you can’t get an eSIM from SKT for data-sharing purposes. If you try online, an error message pops up saying that tablets are not supported for the eSIM generation process, and the customer service agents also say it’s impossible. I also couldn’t find anything scouring the various Korean forums, so I assumed I had to use a USIM again to open up a data-sharing line.&lt;/p&gt;
&lt;p&gt;But when I went to a SKT branch yesterday, I needed two fields – IMEI1 and IMEI2. Most phones have the two systems for eSIMs and USIMs separate, assigning a separate IMEI to each, which makes eSIM generation possible for those devices. However, for tablets supporting eSIM like the iPad, most of them share a single IMEI value between the eSIM and USIM, switching back and forth as necessary, and many SKT employees just give up at this stage. However, after inputting the same IMEI value for both fields, the registration process proceeded normally.&lt;/p&gt;
&lt;p&gt;Of course, this is still against policy, and because data-sharing lines can be frozen or have other issues crop up if you look at it wrong, you need to be aware of the fact that you may potentially waste the eSIM generation fee when you proceed with this process.&lt;/p&gt;
&lt;p&gt;Once the registration process was over and the eSIM QR code was scanned on the iPad camera, the eSIM downloaded without any problems. Honestly, this shouldn’t be such a big surprise since eSIMs and USIMs are the same – only Korean telcos treat them differently. (&lt;del&gt;Because we’re &lt;em&gt;sooooo&lt;/em&gt; special…&lt;/del&gt;)&lt;/p&gt;
&lt;p&gt;If you go for the same thing but the employee won’t do it for you, then I suggest going to a different branch, possibly one run directly by SKT (called 직영점 in Korea). I also got denied a year ago at a different branch. Because this is something that they’re doing against policy, don’t be rude to them if they don’t do it for you and ask them politely or ask another employee.&lt;/p&gt;
&lt;p&gt;As a side node, I thought about why they would need both IMEI values, and decided that the carriers were collecting them to freeze SIMs with non-matching identities. (So in Korea, to prevent burner phones, carriers will automatically freeze both lines if they detect that they have different registered identitites but are tied to the same phone.) Which doesn’t make sense, because there are cases where you may need two SIM cards with different registered identities in the same phone (like if you’re troubleshooting why a given USIM card of your relative won’t work in their phone), and if you really needed a burner phone you’d just buy more of them. I suspect the rule’s really in place just to inconvenience consumers.&lt;/p&gt;
&lt;h2 id=&quot;update&quot;&gt;Update&lt;/h2&gt;
&lt;p&gt;As of May 24th 2024, this method still works as I used it to activate another device on the network.&lt;/p&gt;</content:encoded></item><item><title>Hosting my own short URLs with YOURLS and Docker</title><link>https://ericswpark.com/blog/2023/2023-11-29-hosting-my-own-short-urls-with-yourls-and-docker/</link><guid isPermaLink="true">https://ericswpark.com/blog/2023/2023-11-29-hosting-my-own-short-urls-with-yourls-and-docker/</guid><pubDate>Wed, 29 Nov 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I’ve always wanted to self-host a short URL service. Mainly because sometimes you need to hand out links, and links expire. (Unless you manage to cheat entropy and the decay of the universe.) So if I have my own short URL service, I can hand out what is essentially a permalink, because I can update it until the heat death of the universe or when my server shuts down because I forgot to pay the bill on time, whichever is faster.&lt;/p&gt;
&lt;p&gt;Anyway, I set it up with YOURLS and Docker (+ Docker Compose). I thought it was going to be difficult, but it’s literally this file:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;docker-compose.yml&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;version&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;3&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  mariadb&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    image&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;mariadb:11&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    container_name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;yourls-mariadb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    restart&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;unless-stopped&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    environment&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      MYSQL_ROOT_PASSWORD&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;ROOT_PASSWORD_HERE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      MYSQL_PASSWORD&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;DATABASE_PASSWORD_HERE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      MYSQL_DATABASE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;yourls&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      MYSQL_USER&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;yourls&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    volumes&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;./mariadb:/var/lib/mysql&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  yourls&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    image&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;yourls:latest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    container_name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;yourls-app&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    restart&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;unless-stopped&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    depends_on&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;mariadb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    environment&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      YOURLS_DB_HOST&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;mariadb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      YOURLS_DB_USER&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;yourls&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      YOURLS_DB_PASS&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;DATABASE_PASSWORD_HERE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      YOURLS_DB_NAME&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;yourls&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      YOURLS_USER&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;ADMIN_USERNAME_HERE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      YOURLS_PASS&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;ADMIN_PASSWORD_HERE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      YOURLS_SITE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://yourls.example.com&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      YOURLS_UNIQUE_URLS&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      YOURLS_PRIVATE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    volumes&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;./yourls:/var/www/html/user&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  swag&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    image&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;linuxserver/swag:latest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    container_name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;swag&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    restart&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;unless-stopped&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    environment&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;PUID=1000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;PGID=1000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;TZ=Etc/UTC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;URL=yourls.example.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;VALIDATION=http&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;EMAIL=letsencrypt-email@example.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    volumes&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;./swag/config:/config&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    ports&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;80:80&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;443:443&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    depends_on&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;yourls&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once you have it up and running with &lt;code&gt;docker-compose up -d&lt;/code&gt;, you do need to move the sample configuration file in the &lt;code&gt;swag/&lt;/code&gt; folder and adjust some settings, but that’s not too difficult.&lt;/p&gt;
&lt;p&gt;Here’s a short link back to this blog post: &lt;a href=&quot;https://links.ericswpark.com/blog-yourls&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://links.ericswpark.com/blog-yourls&lt;/a&gt; (so meta!)&lt;/p&gt;</content:encoded></item><item><title>Automatically generate captions for my videos with Whisper</title><link>https://ericswpark.com/blog/2023/2023-08-08-automatically-generate-captions-for-my-videos-with-whisper/</link><guid isPermaLink="true">https://ericswpark.com/blog/2023/2023-08-08-automatically-generate-captions-for-my-videos-with-whisper/</guid><pubDate>Tue, 08 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I hate writing captions for my video, because it’s such a time-consuming process. So when I discovered &lt;a href=&quot;https://github.com/openai/whisper&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;OpenAI’s Whisper program (and model)&lt;/a&gt;, and &lt;a href=&quot;https://github.com/ggerganov/whisper.cpp/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;the re-write in C++ made by Georgi Gerganov&lt;/a&gt;, I knew I had to try it out to save myself hours of transcribing pain.&lt;/p&gt;
&lt;p&gt;After generating an MP3 file in FCPX, I used &lt;code&gt;ffmpeg&lt;/code&gt; to convert it to a WAV file that Whisper.cpp expects:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ffmpeg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -i&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; input.mp3&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -acodec&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; pcm_s16le&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -ac&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -ar&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 16000&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; output.wav&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then I installed Whisper.cpp:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; clone&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; https://github.com/ggerganov/whisper.cpp.git&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; whisper.cpp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# You probably don&apos;t need large, but that&apos;s what I used&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;bash&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ./models/download-ggml-model.sh&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; large&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;make&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; large&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Adjust threads to your computer&apos;s CPU&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;./main&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -t&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 10&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --output-srt&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --language&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; en&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --model&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ./models/ggml-large.bin&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --file&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ~/Downloads/output.wav&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The audio file I gave it was nearly 12 minutes in length, and Whisper.cpp took about 5 minutes in total to process it.&lt;/p&gt;
&lt;p&gt;After it spat out an SRT file, I imported it into FCPX and had a look. While there were some typos that I had to correct, it was nearly 90% correct in deciphering my terrible, terrible voice. The only minor gripe I had was that each line started with a space, which I thought was probably a bug in Whisper.cpp converting the VTT generated by Whisper into SRT, but other than that it was perfect. I no longer have to manually type out my own voice.&lt;/p&gt;</content:encoded></item><item><title>Smooth scrubbing videos, version 2</title><link>https://ericswpark.com/blog/2023/2023-08-07-smooth-scrubbing-videos-version-2/</link><guid isPermaLink="true">https://ericswpark.com/blog/2023/2023-08-07-smooth-scrubbing-videos-version-2/</guid><pubDate>Sun, 06 Aug 2023 23:48:32 GMT</pubDate><content:encoded>&lt;p&gt;This is an update to &lt;a href=&quot;/blog/2022/2022-11-07-smooth-scrubbing-videos&quot;&gt;the original blog post&lt;/a&gt;. I decided to write this quick update blog post because I found some new resources and techniques on re-encoding to get a smooth scrubbing video.&lt;/p&gt;
&lt;h1 id=&quot;prores&quot;&gt;ProRes?&lt;/h1&gt;
&lt;p&gt;Apple’s ProRes stores video frames “uncompressed”, which means computers don’t have to spend a ton of time decoding them, compared to other compressed codecs like H264 and so on.&lt;/p&gt;
&lt;p&gt;Here’s an example command that you can use quickly:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg -i input.mp4 \   # input file&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -c:v prores_ks    \   # ProRes encoder to use (prores and prores_aw exists, BUT)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -profile:v 3      \   # ProRes profile to use (see below)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -qscale:v 11      \   # The &quot;quality&quot; of the output (9-11 is a good range; lower is better)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -vendor apl0      \   # Tricks Apple programs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  output.mov            # Must be a container that supports ProRes (the other two being mkv and mxf)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;encoder&quot;&gt;Encoder&lt;/h2&gt;
&lt;p&gt;Do not use &lt;code&gt;prores&lt;/code&gt; or &lt;code&gt;prores_aw&lt;/code&gt;. I know it’s shown as an option above, but the two implementations are slow and bad. Just use &lt;code&gt;prores_ks&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;profile&quot;&gt;Profile&lt;/h2&gt;
&lt;p&gt;ffmpeg ProRes profiles are mapped to the following integer values:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;proxy - &lt;code&gt;0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;lt - &lt;code&gt;1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;standard - &lt;code&gt;2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;hq - &lt;code&gt;3&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;4444 - &lt;code&gt;4&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;4444xq - &lt;code&gt;5&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Use &lt;code&gt;3&lt;/code&gt;, unless you need 4:4:4 over 4:2:2 chroma subsampling. In which case you will also need to pass in the &lt;code&gt;-pix_fmpt yuva444p101e&lt;/code&gt; parameter. (If you don’t what this means use &lt;code&gt;3&lt;/code&gt; or below.)&lt;/p&gt;
&lt;h2 id=&quot;qscale&quot;&gt;qscale&lt;/h2&gt;
&lt;p&gt;This parameter is discussed in the documentation below, but brass tacks for fast reading:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;0 is the best, 32 is the worst&lt;/li&gt;
&lt;li&gt;Recommended: 9-13&lt;/li&gt;
&lt;li&gt;Overkill (space is of no concern): 5 or below&lt;/li&gt;
&lt;li&gt;Warning: approaching 0 will cause the video file to be unplayable as it would be too big for machines to handle!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I got this command from &lt;a href=&quot;https://trac.ffmpeg.org/wiki/Encode/VFX#Prores&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;the official ffmpeg documentation&lt;/a&gt;, so if you need more details go check it out there.&lt;/p&gt;
&lt;p&gt;And unfortunately, unlike the H264 method below, I have been unable to use hardware accelerated encoders on ProRes, as it keeps giving me a weird, nonsensical error each time I attempt it. If you know how to fix this please leave a comment and I’ll update this section!&lt;/p&gt;
&lt;h1 id=&quot;h264&quot;&gt;H264&lt;/h1&gt;
&lt;p&gt;I need to make a slight correction to making smooth-scrubbing H264 videos.&lt;/p&gt;
&lt;p&gt;After reading &lt;a href=&quot;https://trac.ffmpeg.org/wiki/Encode/VFX#Frame-by-Framescrubbing&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;ffmpeg’s official documentation&lt;/a&gt;, to get the &lt;strong&gt;smoothest&lt;/strong&gt; scrubbable videos, you need to eliminate the P-frames as well as the B-frames. And here’s the updated command to do so:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg -i input.mp4 \   # input file&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -c:v libx264      \   # H264 encoder&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -profile:v main   \   # H264 profile&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -g 1              \   # Eliminate P frames&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -crf 9            \   # The &quot;quality&quot; of the output (7-11 is a good range; lower is better)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -bf 0             \   # Eliminate B frames&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  -vendor apl0      \   # Tricks Apple programs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  output.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And a quick last pro-tip: if you have an Apple Silicon Mac, you can use hardware acceleration to speed up the video encoding! Just replace &lt;code&gt;-c:v libx264&lt;/code&gt; with &lt;code&gt;-c:v h264_videotoolbox&lt;/code&gt;, and replace the &lt;code&gt;-crf 9&lt;/code&gt; parameter with &lt;code&gt;-b:v 8000k&lt;/code&gt; (as the encoder only supports constant bitrate settings and not crf). Adjust the value as necessary, depending on the source file.&lt;/p&gt;
&lt;p&gt;OK I said last tip but this is really the last one: &lt;a href=&quot;https://github.com/althonos/ffpb&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;check out ffpb&lt;/a&gt; as it gives you a progress bar for ffmpeg jobs!&lt;/p&gt;</content:encoded></item><item><title>Tip on faulty SD cards in GL.iNet routers</title><link>https://ericswpark.com/blog/2023/2023-08-03-tip-on-faulty-sd-cards-in-gl-inet-routers/</link><guid isPermaLink="true">https://ericswpark.com/blog/2023/2023-08-03-tip-on-faulty-sd-cards-in-gl-inet-routers/</guid><pubDate>Wed, 02 Aug 2023 15:33:38 GMT</pubDate><content:encoded>&lt;p&gt;After spending about an hour wrestling with my travel router to get it to recognize my SD card, I decided to write this blog post to save someone the headache and pain I went through.&lt;/p&gt;
&lt;p&gt;I had an SD card that worked fine in all devices but the GL.iNet router. Now, your first thought is probably the formatting of the SD card, right? Well, I tried everything. Both GPT and MBR for the partition table, and &lt;code&gt;ext4&lt;/code&gt;, exFAT, NTFS, and FAT32 for the actual formatting of the partition. But none of the combinations worked.&lt;/p&gt;
&lt;p&gt;SSH-ing into the router, I found the following entry in the &lt;code&gt;dmesg&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[  280.757666] mmc0: mmc_rescan_try_freq: reset power&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This line kept popping up whenever I ejected and inserted the SD card.&lt;/p&gt;
&lt;p&gt;On a whim, I tried a different SD card, a known good one from Kingston. And that’s when the router immediately recognized the card. In the &lt;code&gt;dmesg&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[  444.254378] mmc0: new ultra high speed SDR104 SDHC card at address 0007&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[  444.264841] mmcblk0: mmc0:0007 SD16G 14.4 GiB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[  444.265892]  mmcblk0: p1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And when the card was ejected:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[  448.910935] mmc0: card 0007 removed&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I looked at the other SD card that hadn’t worked. Some no-name brand SD card. I decided it had failed just enough to not meet the router’s tolerances, snapped it in half, and threw it in the bin.&lt;/p&gt;
&lt;p&gt;So pro-tip: if your device stops detecting an SD card, try a different one.&lt;/p&gt;
&lt;p&gt;PS: the formatting turned out to not matter much after all. The router (that I have, the GL-AXT1800, anyway) supports exFAT, FAT32, NTFS, and even &lt;code&gt;ext4&lt;/code&gt;. I’m not sure whether or not it recognizes GPT partition tables, but given that it’s running OpenWRT underneath I would be very surprised if it didn’t.&lt;/p&gt;</content:encoded></item><item><title>Exway doesn&apos;t care about USB-C conformity</title><link>https://ericswpark.com/blog/2023/2023-07-26-exway-doesnt-care-about-usb-c-conformity/</link><guid isPermaLink="true">https://ericswpark.com/blog/2023/2023-07-26-exway-doesnt-care-about-usb-c-conformity/</guid><pubDate>Wed, 26 Jul 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Today was the first time I charged my Exway remote after getting the electric skateboard in May or so. When I went to plug it in with my USB-C charger, I noticed that the remote didn’t light up.&lt;/p&gt;
&lt;p&gt;My heart sank a little when I noticed that the other end of the type-C cable was connected to the type-C port on my charger. If what I thought was true, it would be another device that the engineers didn’t put care into. Sure enough, when I plugged the remote in with a USB-A to type-C cable, it immediately lit up and started to charge.&lt;/p&gt;
&lt;p&gt;So in this blog post, I’ll go over my support ticket to Exway, Exway’s response, and why you should care and &lt;strong&gt;not&lt;/strong&gt; accept devices that violate the USB-C specifications.&lt;/p&gt;
&lt;h1 id=&quot;my-support-ticket-with-exway&quot;&gt;My support ticket with Exway&lt;/h1&gt;
&lt;p&gt;My initial report:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;From: &lt;a href=&quot;mailto:exway@ericswpark.com&quot;&gt;exway@ericswpark.com&lt;/a&gt;  &lt;br&gt;
Title: Exway remote missing USB-C resistor &lt;br&gt;
To: &lt;a href=&quot;mailto:service@exwayboard.com&quot;&gt;service@exwayboard.com&lt;/a&gt; &lt;br&gt;
Date: Wed, 26 Jul 2023 13:24:11 +0900&lt;/p&gt;
&lt;p&gt;Hi,&lt;/p&gt;
&lt;p&gt;I noticed that the remote for the Exway Flex has a missing resistor on the USB-C port. This makes it so that it will not charge with a type-C to C cable. Is this problem fixed with newer revisions of the remote?&lt;/p&gt;
&lt;p&gt;Thanks, &lt;br&gt;
Eric&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Their response:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;From: Support &lt;a href=&quot;mailto:service@exwayboard.com&quot;&gt;service@exwayboard.com&lt;/a&gt; &lt;br&gt;
Title: [Exway Board] Re: Exway remote missing USB-C resistor &lt;br&gt;
To: Exway &lt;a href=&quot;mailto:exway@ericswpark.com&quot;&gt;exway@ericswpark.com&lt;/a&gt; &lt;br&gt;
Date: Wed, 26 Jul 2023 08:12:09 +0000&lt;/p&gt;
&lt;p&gt;Hi Eric,&lt;/p&gt;
&lt;p&gt;Thanks for reaching&lt;/p&gt;
&lt;p&gt;The new remote is using the USB-C charge port&lt;/p&gt;
&lt;p&gt;Best &lt;br&gt;
Exway after-sale support team&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;em&gt;Oh no. They’re not even reading my email…&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;From: &lt;a href=&quot;mailto:exway@ericswpark.com&quot;&gt;exway@ericswpark.com&lt;/a&gt; &lt;br&gt;
Title: Re: [Exway Board] Exway remote missing USB-C resistor &lt;br&gt;
To: Support &lt;a href=&quot;mailto:service@exwayboard.com&quot;&gt;service@exwayboard.com&lt;/a&gt; &lt;br&gt;
Date: Wed, 26 Jul 2023 17:42:40 +0900&lt;/p&gt;
&lt;p&gt;Hi Exway,&lt;/p&gt;
&lt;p&gt;I think you misunderstood my question. My remote does indeed have a USB-C port. However, it is missing the proper resistor that tells the connected type-C cable that it draws power. As a result the remote does not charge with a type-C-to-C cable.&lt;/p&gt;
&lt;p&gt;My question was, whether or not this is fixed in newer revisions of the remote.&lt;/p&gt;
&lt;p&gt;Thanks, &lt;br&gt;
Eric&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And their response:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;From: Support &lt;a href=&quot;mailto:service@exwayboard.com&quot;&gt;service@exwayboard.com&lt;/a&gt; &lt;br&gt;
Title: [Exway Board] Re: Exway remote missing USB-C resistor &lt;br&gt;
To: Exway &lt;a href=&quot;mailto:exway@ericswpark.com&quot;&gt;exway@ericswpark.com&lt;/a&gt; &lt;br&gt;
Date: Wed, 26 Jul 2023 08:59:22 +0000&lt;/p&gt;
&lt;p&gt;Hi Eric,&lt;/p&gt;
&lt;p&gt;Thanks for getting back&lt;/p&gt;
&lt;p&gt;Then we suggest you change the cable to USB- C port, C-C cable is still not able to be compatible&lt;/p&gt;
&lt;p&gt;Best &lt;br&gt;
Exway after-sale support team&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That was kind of the caring and thoughtful response I was expecting from a Chinese company, but I tried again anyway:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;From: &lt;a href=&quot;mailto:exway@ericswpark.com&quot;&gt;exway@ericswpark.com&lt;/a&gt; &lt;br&gt;
Title: Re: [Exway Board] Exway remote missing USB-C resistor &lt;br&gt;
To: Support &lt;a href=&quot;mailto:service@exwayboard.com&quot;&gt;service@exwayboard.com&lt;/a&gt; &lt;br&gt;
Date: Wed, 26 Jul 2023 18:00:54 +0900&lt;/p&gt;
&lt;p&gt;Hi Exway,&lt;/p&gt;
&lt;p&gt;Thank you for the confirmation. Is a fix planned in future revisions? Because the device violates USB-C specifications by not charging with C-to-C cables.&lt;/p&gt;
&lt;p&gt;Thanks, &lt;br&gt;
Eric&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Once again, they misinterpreted my message:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;From: Support &lt;a href=&quot;mailto:service@exwayboard.com&quot;&gt;service@exwayboard.com&lt;/a&gt; &lt;br&gt;
Title: [Exway Board] Re: Exway remote missing USB-C resistor &lt;br&gt;
To: Exway &lt;a href=&quot;mailto:exway@ericswpark.com&quot;&gt;exway@ericswpark.com&lt;/a&gt; &lt;br&gt;
Date: Wed, 26 Jul 2023 09:04:09 +0000&lt;/p&gt;
&lt;p&gt;Hi Eric,&lt;/p&gt;
&lt;p&gt;Thanks for getting back&lt;/p&gt;
&lt;p&gt;The remote just doesn’t have the agreement, not able to change it, thanks for your support&lt;/p&gt;
&lt;p&gt;Best &lt;br&gt;
Leo&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So I asked about hardware:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;From: &lt;a href=&quot;mailto:exway@ericswpark.com&quot;&gt;exway@ericswpark.com&lt;/a&gt; &lt;br&gt;
Title: Re: [Exway Board] Exway remote missing USB-C resistor &lt;br&gt;
To: Support &lt;a href=&quot;mailto:service@exwayboard.com&quot;&gt;service@exwayboard.com&lt;/a&gt; &lt;br&gt;
Date: Wed, 26 Jul 2023 18:06:59 +0900&lt;/p&gt;
&lt;p&gt;Hi Leo,&lt;/p&gt;
&lt;p&gt;Yes, I understand that a firmware update will not resolve this issue, as it is a hardware problem.&lt;/p&gt;
&lt;p&gt;However, for future revisions of this remote, this problem should be fixed, as USB-C spec conformity is very important for all devices shipping with a type-C port. I hope that this is forwarded over to the engineers so that they can incorporate it in future remote revisions. In fact, devices that do not meet this spec can be determined as defective in some jurisdictions.&lt;/p&gt;
&lt;p&gt;Thanks, &lt;br&gt;
Eric&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And their final response:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;From: Support &lt;a href=&quot;mailto:service@exwayboard.com&quot;&gt;service@exwayboard.com&lt;/a&gt; &lt;br&gt;
Title: [Exway Board] Re: Exway remote missing USB-C resistor &lt;br&gt;
To: Exway &lt;a href=&quot;mailto:exway@ericswpark.com&quot;&gt;exway@ericswpark.com&lt;/a&gt; &lt;br&gt;
Date: Wed, 26 Jul 2023 09:22:05 +0000&lt;/p&gt;
&lt;p&gt;Hi Eric,&lt;/p&gt;
&lt;p&gt;I have reported your request and I referred this to the tech team, but I  got a negative answer, sorry about that&lt;/p&gt;
&lt;p&gt;Best &lt;br&gt;
Leo&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So, as of right now, it seems Exway’s stance on this issue is that it is a known issue, but not something they’re willing to address, and something they have no future plans to fix.&lt;/p&gt;
&lt;h1 id=&quot;what-is-the-problem-again&quot;&gt;What is the problem again?&lt;/h1&gt;
&lt;p&gt;USB-C ports are different from other ports [citation needed]. Specifically, unlike USB-A ports that give out 5 volts of power by default, USB-C requires a negotiation process before it’ll start handing out power. You may have heard about this on branding material of USB-C chargers: USB-PD, or USB Power Delivery, is the spec that devices must adhere to.&lt;/p&gt;
&lt;p&gt;But PD negotiation chips are expensive, when compared to the overall price of the device. The electric skateboard remote in question probably costs five dollars or so to make. (In reality, it’s probably much less than that, if you mass-manufacture it.) Having a chip that costs half a dollar takes up a ton of the budget in the BoM (bill-of-materials).&lt;/p&gt;
&lt;p&gt;But obviously, the USB-IF board thought of that, and they provide guidance on how devices should behave (or in this case, identify themselves) if they just want 5V of power, no power delivery negotiation required.&lt;/p&gt;
&lt;p&gt;This is done by pulling down the CC pins to ground with 5.1K resistors. (Because there are two CC pins – CC1 and CC2 - you need two of them.) Failure to do so means that with a type-C-to-C cable and charger won’t work with said device – the charger will check the CC lines over the type-C cable, find it “floating” (as in, not pulled down with a resistor), and not send over the required 5V. (And if your device shipped with one resistor, &lt;a href=&quot;https://medium.com/@leung.benson/how-to-design-a-proper-usb-c-power-sink-hint-not-the-way-raspberry-pi-4-did-it-f470d7a5910&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;or shares a resistor for the two CC pins&lt;/a&gt; (ugh Raspberry Pi Foundation!!!), then it can lead to all sorts of wonky behavior, like the device refusing to charge if the cable is plugged in one way and charging normally when the port is flipped.)&lt;/p&gt;
&lt;p&gt;In this case, Exway didn’t put in the required two resistors on the CC pins of the remote, and as a result it won’t charge with USB-C-to-C cables.&lt;/p&gt;
&lt;h1 id=&quot;so-what&quot;&gt;So what?&lt;/h1&gt;
&lt;p&gt;I know what you’re thinking. “Just charge it with a USB-A to C cable! What’s the big deal here?”&lt;/p&gt;
&lt;p&gt;Sure, you can charge the remote with this workaround. But let’s look at the bigger picture.&lt;/p&gt;
&lt;p&gt;These resistors cost &lt;del&gt;pennies&lt;/del&gt; &lt;a href=&quot;https://github.com/ericswpark/ericswpark.github.io-comments/discussions/7#discussioncomment-7836362&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;a fraction of a penny&lt;/a&gt; (thanks @rickcox on GitHub!) to include. And the instructions on how to do that is a simple Google search away. In fact, if you’re a competent electrical engineer designing circuits, you know your first job is to read the specifications before implementing them on your device.&lt;/p&gt;
&lt;p&gt;Which means one of the following is true:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Exway hired incompetent engineers that do a poor job, or&lt;/li&gt;
&lt;li&gt;The engineers warned that the port did not follow specifications, but Exway decided not to fix it to save money, or&lt;/li&gt;
&lt;li&gt;The engineering team at Exway made a geniune mistake and forgot to include the resistors, or didn’t know about this particular specification (I mean, the USB-C spec is really long, and probably very complicated thanks to the USB-IF committee).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But I don’t think the third one is likely. Nobody caught the problem while testing this product during development? Nobody used a type-C-to-C cable to charge the remote while using engineering samples? No, it is borderline impossible for Exway to not be aware of this problem, &lt;em&gt;unless&lt;/em&gt; they don’t do any sort of testing, which again I find hard to believe. (And if they don’t test their products, that’s even more horrifying.)&lt;/p&gt;
&lt;p&gt;And now we know they’re aware of it, but they don’t care. They don’t plan to fix the problem, even when they’re made aware of it.&lt;/p&gt;
&lt;p&gt;If they’re willing to cut corners on stuff like &lt;em&gt;this&lt;/em&gt;, do you really want to risk your life and ride a electric skateboard from them, equipped with their battery? Even if we give them the benefit of the doubt and say that they did everything possible in regards to safety, I don’t think they would be very receptive to a fix if the battery spontaneously combusted, especially given the response above.&lt;/p&gt;
&lt;h1 id=&quot;so-what-can-i-do&quot;&gt;So what can I do?&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;If you have an Exway board that is affected by this problem&lt;/strong&gt;, then contact Exway and let them know that this is unacceptable. The product is defective by design, and they need to fix it.&lt;/p&gt;
&lt;p&gt;Also, consider discontinuing the use of their skateboards.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you were considering purchasing an electric skateboard&lt;/strong&gt;, consider alternative brands.&lt;/p&gt;
&lt;p&gt;But most importantly, it’s important to spread the word and let manufacturers know that &lt;strong&gt;not conforming to USB-C spec in 2023 is absolutely unacceptable.&lt;/strong&gt; Countless forum posts have been made about this exact issue, and there’s even a subreddit called r/USBCHardware that has constant threads on defective products like these. Help people that might not understand why their device won’t charge with certain cables by not letting these companies get away with bad design.&lt;/p&gt;
&lt;h1 id=&quot;links&quot;&gt;Links&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://www.reddit.com/r/Exway/comments/15a5myg/exway_doesnt_care_about_usbc_conformity/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Discussion on r/Exway&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.reddit.com/r/UsbCHardware/comments/15a5o3f/oc_exway_doesnt_care_about_usbc_conformity/?&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Discussion on r/USBCHardware&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://news.ycombinator.com/item?id=36886859&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Discussion on Hacker News&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>Migrating Homebrew from an Intel to an Apple Silicon Mac</title><link>https://ericswpark.com/blog/2023/2023-06-17-migrating-homebrew-from-intel-to-asi-mac/</link><guid isPermaLink="true">https://ericswpark.com/blog/2023/2023-06-17-migrating-homebrew-from-intel-to-asi-mac/</guid><pubDate>Sat, 17 Jun 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;These are the steps I took to migrate Homebrew from an Intel-based Mac to an Apple Silicon-based (abbreviated as ASi from here on out) Mac.&lt;/p&gt;
&lt;h1 id=&quot;preparations&quot;&gt;Preparations&lt;/h1&gt;
&lt;p&gt;Before moving, run the following command on your old Intel Mac:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;brew bundle dump&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This dumps the currently installed package list to a &lt;code&gt;Brewfile&lt;/code&gt; in the current directory. Save it, or either leave it there if you’re doing a Mac-to-Mac transfer.&lt;/p&gt;
&lt;h2 id=&quot;wait-i-dont-have-my-intel-mac-anymore&quot;&gt;Wait, I don’t have my Intel Mac anymore!&lt;/h2&gt;
&lt;p&gt;Running the above command on an ASi Mac will fail, because that command requires &lt;code&gt;git&lt;/code&gt;, and the Intel version of &lt;code&gt;git&lt;/code&gt; will crash on ASi.&lt;/p&gt;
&lt;p&gt;So after you finish with the ASi Homebrew install below, do the following:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;zsh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# On Intel Homebrew terminal window&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;brew&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; uninstall&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; git&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# On ASi Homebrew terminal window&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;brew&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; git&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# On Intel Homebrew terminal window&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;brew&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; bundle&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; dump&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then proceed, starting with the “Uninstall Homebrew for Intel” section.&lt;/p&gt;
&lt;h1 id=&quot;install-the-asi-homebrew&quot;&gt;Install the ASi Homebrew&lt;/h1&gt;
&lt;p&gt;This step is rather simple. Just run the Homebrew install command again:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;zsh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;/bin/bash&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -c&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;$(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;curl&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -fsSL&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The installer will give you those two commands to run so that Homebrew is on your path:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;zsh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;eval &quot;$(/opt/homebrew/bin/brew shellenv)&quot;&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; /Users/ericswpark/.zprofile&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;eval&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;$(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;/opt/homebrew/bin/brew&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; shellenv)&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(Your command will have your username substituted in, obviously.)&lt;/p&gt;
&lt;h1 id=&quot;uninstall-homebrew-for-intel&quot;&gt;Uninstall Homebrew for Intel&lt;/h1&gt;
&lt;p&gt;Run the following commands:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;zsh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ~/Downloads&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Because wget (Intel version) might not run on ASi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;curl&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; https://raw.githubusercontent.com/Homebrew/install/HEAD/uninstall.sh&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --output&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; uninstall.sh&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;/bin/bash&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ./uninstall.sh&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -p&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /usr/local&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The uninstaller didn’t have the necessary permissions to delete things fully, so I had to go into &lt;code&gt;/usr/local&lt;/code&gt; and delete all files and folders in there after it had finished running. It should &lt;em&gt;usually&lt;/em&gt; be safe to delete everything in that folder since macOS doesn’t even ship with it by default, but if you do have things that depend on stuff inside that folder, I suggest deleting carefully so that you don’t end up breaking something. In my case, I just decided to re-install whatever I broke, so I just deleted everything within that folder.&lt;/p&gt;
&lt;p&gt;Note: &lt;a href=&quot;https://github.com/porg&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;@porg on GitHub&lt;/a&gt; has rightly pointed out that some tools and package managers also install to &lt;code&gt;/usr/local&lt;/code&gt;, so do go through this folder just in case before deleting things.&lt;/p&gt;
&lt;p&gt;You may also need to delete any additions you made to your dotfiles so that the Intel version of Homebrew is in your path. In my case, I had to delete the lines in &lt;code&gt;.zshrc&lt;/code&gt; referencing the previous Homebrew install in &lt;code&gt;/usr/local&lt;/code&gt;.&lt;/p&gt;
&lt;h1 id=&quot;reinstalling-everything&quot;&gt;Reinstalling everything&lt;/h1&gt;
&lt;p&gt;Open a new terminal window so that the ASi-version of Homebrew is active.&lt;/p&gt;
&lt;p&gt;Navigate to the directory with the &lt;code&gt;Brewfile&lt;/code&gt; from earlier and run:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;zsh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;brew&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; bundle&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --file&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ./Brewfile&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you should be good to go!&lt;/p&gt;</content:encoded></item><item><title>These AI detectors are getting out of hand</title><link>https://ericswpark.com/blog/2023/2023-05-06-these-ai-detectors-are-getting-out-of-hand/</link><guid isPermaLink="true">https://ericswpark.com/blog/2023/2023-05-06-these-ai-detectors-are-getting-out-of-hand/</guid><pubDate>Sat, 06 May 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Recently, I noticed a ton of posts on Reddit where students were accused of academic dishonesty and plagiarism through the use of “AI” tools. With the start being this post:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://reddit.com/r/college/comments/136tc9i/i_have_been_falsely_accused_of_using_ai_on_my/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;I have been falsely accused of using AI on my essay&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And after that, the reports just started to flood in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://reddit.com/r/college/comments/138ug6h/i_was_falsely_accused_of_using_a_ai_to_write_my/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;I was falsely accused of using a AI to write my final paper&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://reddit.com/r/college/comments/138xre6/after_seeing_many_false_ai_usage_accusations_here/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;After seeing many false AI usage accusations here, it finally happened to me as well.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The possibilities here are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;All of those students are lying, and they did really use AI&lt;/li&gt;
&lt;li&gt;They aren’t lying, and the AI detection software screwed up&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Seems obvious enough. Let’s figure out how AI detection software works. To do that, we need a base example. Let’s go with Turnitin’s AI detector.&lt;/p&gt;
&lt;h1 id=&quot;why-turnitin&quot;&gt;Why Turnitin?&lt;/h1&gt;
&lt;p&gt;Let’s get the bias out of the way: I have a deeply-seated hatred toward Turnitin.&lt;/p&gt;
&lt;p&gt;Back in high school, Turnitin accused me of plagiarizing an essay that I wrote on my own, and gave one of my classmates a 100% plagiarism score because they submitted the essay on a separate, testing Turnitin environment to make sure they didn’t accidentally plagiarize anything.&lt;/p&gt;
&lt;p&gt;But that’s before the AI tools started to flood the market. Turnitin’s pre-AI tool “checked” for “plagiarism” by essentially doing a compare between all the text submitted to their platform. If two copies matched and the match rate was above a certain threshold it would be flagged for review.&lt;/p&gt;
&lt;p&gt;Obviously, that wouldn’t work anymore with GPT-based cheating. Depending on the prompt and the model and the platform used, the output could vary drastically, even between runs! Turnitin had to come up with a new solution to avoid becoming irrelevant overnight with their already-flawed, outdated method of comparing text content.&lt;/p&gt;
&lt;p&gt;Enter…&lt;/p&gt;
&lt;h1 id=&quot;turnitins-ai-writing-detection&quot;&gt;Turnitin’s “AI writing detection”&lt;/h1&gt;
&lt;p&gt;Turnitin capitalized on the scare of “AI-based cheating” by &lt;a href=&quot;https://www.turnitin.com/solutions/ai-writing&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;creating an entire page&lt;/a&gt; dedicated to showing off their new “AI detection” solution.&lt;/p&gt;
&lt;p&gt;If you ever go to that page accidentally, you’ll see a whole lot of marketing speak, corporate mumbo-jumbo (“AI Innovation Lab”), but nothing of substance. How does the damn detection work, Turnitin?!&lt;/p&gt;
&lt;p&gt;That nugget of info is buried in &lt;a href=&quot;https://www.turnitin.com/products/features/ai-writing-detection&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;their FAQ page&lt;/a&gt;, and even the information listed there is vague and unhelpful. I mean, read this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;How does it work?
(…blah blah technical details…) The segments are run against our AI detection model and we give each sentence a score between 0 and 1 to determine whether it is written by a human or by AI. (…more technical stuff…) Currently, Turnitin’s AI writing detection model is trained to detect content from the GPT-3 and GPT-3.5 language models, which includes ChatGPT. Because the writing characteristics of GPT-4 are consistent with earlier model versions, our detector is able to detect content from GPT-4 (ChatGPT Plus) most of the time. We are actively working on expanding our model to enable us to better detect content from other AI language models.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;Okay, so it appears Turnitin checks if the submission was generated by an AI by… feeding it into another AI model and asking it to predict whether or not it was written by a human or an AI. And look at the last sentence – they need to make a new model for each AI language model that crops up on the market.&lt;/p&gt;
&lt;p&gt;So Turnitin went from a objective measure (how much of the text was copy-pasted) to a completely subjective measure made out of a AI black-box. Obviously, this new system is &lt;em&gt;much more&lt;/em&gt; trustworthy. (/s)&lt;/p&gt;
&lt;p&gt;And they expect instructors to trust them on this and completely ruin students’ lives based on this prediction score!&lt;/p&gt;
&lt;h1 id=&quot;well-actually&quot;&gt;Well, actually…&lt;/h1&gt;
&lt;p&gt;Even Turnitin itself acknowledges that this method of detection is flawed. In their blog post titled &lt;a href=&quot;https://www.turnitin.com/blog/understanding-false-positives-within-our-ai-writing-detection-capabilities&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;“Understanding false positives within our AI writing detection capabilities”&lt;/a&gt;, Turnitin writes the following:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Know before you go—make sure you consider the possibility of a false positive upfront and have a plan for what your process and approach will be for determining the outcome. Even better, communicate that to students so that you have a shared set of expectations.&lt;/li&gt;
&lt;li&gt;Assume positive intent—in this space of so much that is new and unknown, give students the strong benefit of the doubt. If the evidence is unclear, assume students will act with integrity.&lt;/li&gt;
&lt;li&gt;Be open and honest—it is important to acknowledge that there may be false positives upfront, so both the instructor and the student should be prepared to have an open and honest dialogue. If you don’t acknowledge that a false positive may occur, it will lead to a far more defensive and confrontational interaction that could ultimately damage relationships with students.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;So why are these horror stories occurring across different colleges? Well, this disclaimer is tucked away into a blog post, that’s why. Now, I’m not sure if they put a giant warning banner on top of their AI prediction score, but I’m pretty confident in saying that even if they did it won’t read anything like “This score may be completely inaccurate because we’re basically asking an AI to perform a Turing test on fellow robots”, because if they actually wrote that they’d lose the trust of educators and go out of business.&lt;/p&gt;
&lt;p&gt;Therefore, &lt;strong&gt;some&lt;/strong&gt; educators assume the AI detection model of Turnitin is battle-tested and ready for general use, not taking into account that the prediction score itself has a compounded margin of error.&lt;/p&gt;
&lt;p&gt;But let’s stop bashing on Turnitin for a moment. Because I want to talk about “AI detection” in general.&lt;/p&gt;
&lt;h1 id=&quot;ai-detection-models-hallucination-and-datasets&quot;&gt;AI detection, models, “hallucination” and datasets&lt;/h1&gt;
&lt;p&gt;All this “AI detection” sucks because the very idea of “detecting AI” sucks.&lt;/p&gt;
&lt;p&gt;Look at it this way: we’re warned against trusting output from AI models because they may “hallucinate” and spit out completely incorrect information. AI detectors are built off of AI models. So how come we’re asked to trust AI detectors?&lt;/p&gt;
&lt;p&gt;Furthermore, the power of these GPT-based tools come from the giant dataset that they use to give us information. Stuff like writing code, creating a recipe, etc. are easy for AI models to do, since they only have to regurgitate what they learned from the datasets. But with AI detection, the models are asked to operate on a completely new input (generated from the aforementioned GPT models) and determine something that was never included in their dataset – whether or not a given piece of text was AI-generated. The very idea of AI-detection only existed as Turing tests on the Internet, and it never could’ve accounted for all the GPT models flooding the landscape right this moment.&lt;/p&gt;
&lt;p&gt;Case in point: one of the “AI detectors” named GPTZero &lt;a href=&quot;https://www.reddit.com/r/ChatGPT/comments/11ha4qo/gptzero_an_ai_detector_thinks_the_us_constitution/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;actually marked the Declaration of Independence as AI-generated&lt;/a&gt;. &lt;del&gt;I never knew the founding fathers were GPT models!&lt;/del&gt;&lt;/p&gt;
&lt;h1 id=&quot;so-what-now&quot;&gt;So, what now?&lt;/h1&gt;
&lt;p&gt;Instructors around the world need to adapt, just like they did when the Internet first came out and allowed dishonest students to copy-paste whatever they found online.&lt;/p&gt;
&lt;p&gt;Instead of the AI detection prediction score becoming a measure, it should only be used as a reference. If the student performs badly all the time, then it can be used as part of the evidence set to show that the student is committing academic dishonesty. If the student is a model student and one of their essays is flagged as AI-generated, then educators will know that there was a false positive result.&lt;/p&gt;
&lt;p&gt;For this to happen, all these companies should become much more transparent about their detection capabilities, and emphasize that their tool should only be a single part of an arsenal used to determine academic honesty, not a be-all-end-all solution. This may be hard to do, since these tools are sold to educational organizations and distributed in bulk to educators. Without proper documentation and training, educators may have a false understanding of the tool and use it in ways that it wasn’t intended to be used.&lt;/p&gt;
&lt;p&gt;And honestly, writing out that last sentence reminded me of the pre-AI Turnitin. And GPT chatbots. And virtually every other piece of software or tool in existence. If you don’t fully understand the limitations and characteristics of what you’re using, then you probably shouldn’t blindly trust the results when everything’s said and done.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I hope this blog post was useful! If you or any other student you know was misidentified as a robot, try sharing this blog post with your instructor and let them know the dangers of relying on AI detectors.&lt;/p&gt;
&lt;h1 id=&quot;update-2023-07-27&quot;&gt;Update (2023-07-27)&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://news.ycombinator.com/item?id=36862850&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;OpenAI recently pulled their AI detector because it had poor accuracy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When the most popular GPT-based chatbot maker shuts down their detector, it really begs the question: how do other companies manage to make detectors that surpass that of OpenAI? (Hint: they can’t, and don’t.)&lt;/p&gt;</content:encoded></item><item><title>How to reverse proxy properly with Caddy and Docker</title><link>https://ericswpark.com/blog/2023/2023-03-18-how-to-reverse-proxy-properly-with-caddy-and-docker/</link><guid isPermaLink="true">https://ericswpark.com/blog/2023/2023-03-18-how-to-reverse-proxy-properly-with-caddy-and-docker/</guid><pubDate>Sat, 18 Mar 2023 09:52:54 GMT</pubDate><content:encoded>&lt;p&gt;This is a tutorial on how to properly set up Caddy running in a Docker container.&lt;/p&gt;
&lt;h1 id=&quot;the-prerequisites&quot;&gt;The prerequisites&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;A server with the Docker service enabled. Let’s (host)name the server &lt;code&gt;timmy&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Some services you want to expose to the web&lt;/li&gt;
&lt;li&gt;A custom Docker network that houses these services (let’s say &lt;code&gt;proxynetwork&lt;/code&gt; here)&lt;/li&gt;
&lt;li&gt;(optional) Some services you don’t want to expose to the web&lt;/li&gt;
&lt;li&gt;(optional) A VPN service such as Wireguard to access those services&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;why-reverse-proxy&quot;&gt;Why reverse proxy?&lt;/h1&gt;
&lt;p&gt;Because typing &lt;code&gt;service.timmy.example.com&lt;/code&gt; is a whole lot better than &lt;code&gt;192.168.1.200:4343&lt;/code&gt; or &lt;code&gt;timmy:4343&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Especially once you have more than two or three services to access. Good luck remembering all the port numbers!&lt;/p&gt;
&lt;h1 id=&quot;part-1---the-outer-layer&quot;&gt;Part 1 - The outer layer&lt;/h1&gt;
&lt;p&gt;First off, install Caddy with the &lt;code&gt;host&lt;/code&gt; networking option. (I’ll explain why later.) Then in the configuration, map the domains according to your needs:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Set up logging&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;(logs) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    log {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        output file /data/logs/access.log&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Hide the services from search engines&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;(no_robots_txt) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    respond /robots.txt 200 {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        body &quot;User-agent: *&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Disallow: /&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        close&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Main page&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;timmy.example.com {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    # You can either reverse proxy to a service...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    reverse_proxy 192.168.1.200:8080&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    # Or respond like this (uncomment the following line)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    #respond &quot;Hello World!&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    import no_robots_txt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    import logs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Different services&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;*.timmy.example.com {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    import logs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    @plex host plex.timmy.example.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    handle @plex {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        # `172.18.0.1` is the Docker host IP&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        reverse_proxy 172.18.0.1:32400&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        import no_robots_txt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pretty standard-fare stuff. Once you have the container running, Caddy will take care of things like SSL certificate management with Let’s Encrypt, assuming &lt;code&gt;timmy.example.com&lt;/code&gt; is pointing to the public IP of the server, and &lt;code&gt;*.timmy.example.com&lt;/code&gt; is a CNAME to &lt;code&gt;timmy.example.com&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If that’s all you wanted to do, you can stop here! But what if you have some internal services you want to access over VPN? Then read on.&lt;/p&gt;
&lt;h1 id=&quot;part-2---internal-services&quot;&gt;Part 2 - Internal services&lt;/h1&gt;
&lt;p&gt;This is the tricky part, because we want to restrict the people that can access internal services to those that are connected over VPN only.&lt;/p&gt;
&lt;p&gt;Let’s set up the internal Caddy server first. Create the following &lt;code&gt;Caddyfile&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Set up logging&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;(logs) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    log {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        output file /data/logs/access.log&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;:80 {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    @main host timmy.private.example.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    handle @main {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        reverse_proxy foo:8080&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    @jenkins host jenkins.timmy.private.example.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    handle @jenkins {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        reverse_proxy jenkins:8080&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For this to work, &lt;code&gt;timmy.private.example.com&lt;/code&gt; must point to the private IP inside the VPN network, and &lt;code&gt;*.timmy.private.example.com&lt;/code&gt; must be a CNAME of &lt;code&gt;timmy.private.example.com&lt;/code&gt;. You can publish those to the public DNS – they’re internal addresses anyway so it’s not like people can access them.&lt;/p&gt;
&lt;p&gt;Now, you might be wondering why we are using &lt;code&gt;:80&lt;/code&gt; for the server block, instead of something like &lt;code&gt;timmy.private.example.com&lt;/code&gt; or &lt;code&gt;*.timmy.private.example.com&lt;/code&gt;. And that’s because since this is an internal Caddy server that gets reverse-proxied-to by another Caddy server, we don’t want it automatically generating SSL certificates or what not. In fact, having multi-layered HTTPS in a reverse proxy setup is generally a Bad Idea™, which is why we will only listen on the standard HTTP port 80.&lt;/p&gt;
&lt;p&gt;Also, you may have noticed that we are using hostnames to refer to services, like &lt;code&gt;jenkins&lt;/code&gt;. This only works if you use the aforementioned custom Docker network, so you need to make sure that both the service (Jenkins, for example) and the internal Caddy server is on the same network! In this case, I would create the internal Caddy server on the &lt;code&gt;proxynetwork&lt;/code&gt;, and forward port 80 to 680 on the host machine.&lt;/p&gt;
&lt;p&gt;Great, so now all we have to do is join those two together!&lt;/p&gt;
&lt;h1 id=&quot;part-3---joining-the-two-servers&quot;&gt;Part 3 - Joining the two servers&lt;/h1&gt;
&lt;p&gt;On the outer Caddy server, add the following to the &lt;code&gt;Caddyfile&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Reverse proxy to internal Caddy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;timmy.private.example.com {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        import logs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        reverse_proxy 172.18.0.1:680&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;*.timmy.private.example.com {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        import logs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        reverse_proxy 172.18.0.1:680&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you should be able to access the services. All set, right?&lt;/p&gt;
&lt;p&gt;Not so fast!&lt;/p&gt;
&lt;h1 id=&quot;not-so-secure&quot;&gt;Not so secure&lt;/h1&gt;
&lt;p&gt;You’d think that since &lt;code&gt;timmy.private.example.com&lt;/code&gt; points to a private IP address inside the VPN network, people on the Internet cannot access those services.&lt;/p&gt;
&lt;p&gt;But see the &lt;code&gt;host&lt;/code&gt; keyword? Caddy checks what host the client is visiting and forwards them to the appropriate blocks. So what does Caddy check? It checks the &lt;code&gt;Host&lt;/code&gt; header embedded into every HTTP/S request.&lt;/p&gt;
&lt;p&gt;Do you see the problem yet? HTTP/S requests are made by clients, and their contents can be arbitrary.&lt;/p&gt;
&lt;p&gt;Try a simple test: disconnect from your VPN network, open up the &lt;code&gt;hosts&lt;/code&gt; file on your operating system, and add an entry that points &lt;code&gt;timmy.private.example.com&lt;/code&gt; to the public IP of your server. Then visit &lt;code&gt;timmy.private.example.com&lt;/code&gt;. What do you see?&lt;/p&gt;
&lt;p&gt;Because this check can easily be bypassed with something so simple as a &lt;code&gt;hosts&lt;/code&gt; file, it is NOT secure, so we have to do something about it.&lt;/p&gt;
&lt;h1 id=&quot;part-4---hardening-the-outer-shell&quot;&gt;Part 4 - Hardening the outer shell&lt;/h1&gt;
&lt;p&gt;Back at the outer Caddy server layer, add the following snippet:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;(block_nonvpn) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    @notVPN not remote_ip 10.10.0.0/24&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    handle @notVPN {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        respond &quot;Access denied&quot; 403&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You will need to replace &lt;code&gt;10.10.0.0/24&lt;/code&gt; with the actual IP range of your VPN network.&lt;/p&gt;
&lt;p&gt;You might be tempted to not respond at all, and terminate the session immediately upon figuring out that a public IP is trying to access a private server block:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;(block_nonvpn) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    @notVPN not remote_ip 10.10.0.0/24&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    handle @notVPN {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        abort&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, this is not recommended, as you can set up something like &lt;code&gt;fail2ban&lt;/code&gt; to find abusers with the 403 entries in the access log, and ban them from the network and reduce load.&lt;/p&gt;
&lt;p&gt;Of course, for this to work, the outer Caddy layer must be in the &lt;code&gt;host&lt;/code&gt; network mode. In my experiments, any other network mode causes Caddy to recognize all clients as originating from &lt;code&gt;172.18.0.1&lt;/code&gt;, or the internal IP address of the Docker network layer that points to the Docker machine host.&lt;/p&gt;
&lt;p&gt;Then, add the snippet to the private services, like so:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Reverse proxy to internal Caddy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;timmy.private.example.com {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        import logs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        import block_nonvpn&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        reverse_proxy 172.18.0.1:680&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;*.timmy.private.example.com {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        import logs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        import block_nonvpn&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        reverse_proxy 172.18.0.1:680&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you try the &lt;code&gt;hosts&lt;/code&gt; file trick again, you should see that it no longer works!&lt;/p&gt;
&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;I hope this helps with configuring Caddy for reverse-proxying to different Docker services!&lt;/p&gt;
&lt;h1 id=&quot;thanks-to&quot;&gt;Thanks to&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://caddy.community/t/subdomains-separating-public-and-private-services/19331/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;@fvbommel on the Caddy forums for the security advice&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Handwashing on Apple Watch is useless</title><link>https://ericswpark.com/blog/2023/2023-02-04-handwashing-on-apple-watch-is-useless/</link><guid isPermaLink="true">https://ericswpark.com/blog/2023/2023-02-04-handwashing-on-apple-watch-is-useless/</guid><pubDate>Sat, 04 Feb 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;By far, the most disappointing and useless feature on the Apple Watch is the Handwashing Timer. Which is ironic, because when it was announced back in 2020 I thought it was a really neat idea.&lt;/p&gt;
&lt;p&gt;The problem: the trigger… doesn’t trigger.&lt;/p&gt;
&lt;p&gt;Apple says &lt;a href=&quot;https://support.apple.com/en-us/HT211206&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;the trigger is audio and motion&lt;/a&gt;, so it probably detects the sound of running water, then checks if the user is rubbing their hands together.&lt;/p&gt;
&lt;p&gt;But this never works reliably. When it does work it is magical. But more often then not it thinks I’ve only washed for five seconds when I’ve already scrubbed for forty seconds trying to get it to trigger, or won’t trigger at all.&lt;/p&gt;
&lt;p&gt;And there are false positives: any rubbing motion while water is running sets it off. So it activates when you’re washing dishes, for example.&lt;/p&gt;
&lt;p&gt;My naive solution: when the Apple Watch hears the sound of running water, prompt the user if they’re washing their hands. But since we obviously can’t touch the watch with dirty hands, have the user do some sort of gesture to start the timer. Maybe like a double-chop in mid-air, just like the Motorola phones that wait for the gesture to turn on the flashlight?&lt;/p&gt;
&lt;p&gt;This solves the issue of false positives, since users can just not do the gesture to not start the timer if they’re not washing their hands, and it also means it’s easier to trigger, since the only condition to fulfill is to get the Apple Watch to hear the sound of running water.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;update-2023-04-15&quot;&gt;Update (2023-04-15)&lt;/h2&gt;
&lt;p&gt;So after a ton of trial and error, I got frustrated enough to research exactly what the trigger for handwashing on the Apple Watch is. And it seems like it isn’t water.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Brian Heater (&lt;a href=&quot;https://twitter.com/bheater&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;@bheater&lt;/a&gt;) on &lt;a href=&quot;https://techcrunch.com/2020/06/29/apple-began-work-on-the-watchs-hand-washing-feature-years-before-covid-19/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;TechCrunch&lt;/a&gt;:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The system uses machine learning models to tackle different methods, but the system gets an additional nudge from the Watch’s microphone. Along with motion, the app listens for the sound of running water. Even that’s not enough, though — after all, eco sinks have become increasingly popular, meaning that there’s often less water sound to be listening for. The sound of squishing soap takes care of that last bit. It’s got a unique enough audio signature so as to confirm that handwashing is taking place.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://www.macstories.net/author/alexguyot/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Alex Guyot&lt;/a&gt; on &lt;a href=&quot;https://www.macstories.net/stories/watchos-7-the-macstories-review/#handwashing-detection&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;MacStories&lt;/a&gt;:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Handwashing detection uses a combination of wrist movement and ambient sound tracking to make its determinations. Apple says that the unique squelching sound of soap is one of the main indicators. (…)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://www.cnn.com/profiles/jacob-krol&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Jacob Krol&lt;/a&gt; on &lt;a href=&quot;http://edition.cnn.com/cnn-underscored/electronics/watch-os-7-preview-public-beta&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;CNN Underscored&lt;/a&gt;:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;(…) And the Apple Watch will now track your hand-washing in two core ways: It will monitor and listen for the soap pump, as well as water and wrist movement, to ensure you’re washing your hands for 20 seconds, and will remind you to wash your hands when you return home. (…)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Which makes a ton of sense, because then the Apple Watch wouldn’t detect any handwashing sessions where the water has been switched off, so as to not waste any. So I tried making the squelching sounds obvious as much as possible during handwashing, and it seems to have improved the detection rate by quite a bit. I haven’t been nerdsniped to such a degree as to actually track the detection rate, but in my subjective thoughts I think the rate has gone from 10% to around 70 or 80%.&lt;/p&gt;</content:encoded></item><item><title>Get rid of Naver&apos;s fearmongering browser banner</title><link>https://ericswpark.com/blog/2023/2023-01-24-get-rid-of-navers-fearmongering-browser-banner/</link><guid isPermaLink="true">https://ericswpark.com/blog/2023/2023-01-24-get-rid-of-navers-fearmongering-browser-banner/</guid><pubDate>Tue, 24 Jan 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;What the hell is this, Naver?&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;naver-banner&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;3360&quot; height=&quot;436&quot; src=&quot;/_astro/naver-banner.BAbbdOsB_ZaYelC.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;No, I’m not using an outdated browser. Naver is just pushing for their self-developed browser, Naver Whale. The problem is that the banner makes users believe that the browser they’re currently using is vulnerable in terms of security, when it might not be (as I get this banner on the latest versions of Firefox and Safari).&lt;/p&gt;
&lt;p&gt;But Naver Whale isn’t some special browser. It’s just a skin and some customizations on top of Google’s Chromium (which is a base of Google Chrome). As Naver has to re-modify and re-distribute Chromium every time Google releases a new version, Naver Whale could actually be slower in distributing security updates.&lt;/p&gt;
&lt;p&gt;So how do we get rid of this banner? We can just block it with our browser’s ad-blockers. The HTML element to block is as follows:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;html&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; id&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;NM_TOP_BANNER&quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To convert this into an ad-block rule (on uBlock Origin, for example):&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;###NM_TOP_BANNER&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt=&quot;ad-block&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1198&quot; height=&quot;586&quot; src=&quot;/_astro/ad-block.CUZ10aZP_Z1O4vIW.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;I wish they’d refrain from false advertising. Actually, wasn’t it illegal in the first place?&lt;/p&gt;</content:encoded></item><item><title>TextEdit leaving behind weird folders on SMB</title><link>https://ericswpark.com/blog/2023/2023-01-23-textedit-leaving-behind-weird-folders-on-smb/</link><guid isPermaLink="true">https://ericswpark.com/blog/2023/2023-01-23-textedit-leaving-behind-weird-folders-on-smb/</guid><pubDate>Mon, 23 Jan 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When saving text files to an SMB server with TextEdit on macOS, you may run into this issue where it leaves behind a ton of directories every save, like so:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;name-of-text-file.txt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;name-of-text-file.txt.sb-xxxxxxxx-xxxxxx&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where &lt;code&gt;x&lt;/code&gt;s are replaced by some random string of characters and numbers.&lt;/p&gt;
&lt;p&gt;And sometimes, TextEdit will completely fail to save a file.&lt;/p&gt;
&lt;p&gt;Well, it might be because you’re disallowing dot files on your SMB server with something like the following configuration:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Get rid of macOS dot files crap (fuck you macOS)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;veto files = /._*/.DS_Store/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While this does disallow macOS dot files it also breaks saving with TextEdit, because these &lt;code&gt;.sb&lt;/code&gt; directories house &lt;code&gt;._name-of-text-file.txt&lt;/code&gt; files, which is presumably used to save file attributes.&lt;/p&gt;
&lt;p&gt;I dislike TextEdit doing this, and I’m pretty sure there is some solution to turn off the functionality, but since this is a server used by multiple people, many of whom are tech illiterate, I just decided to remove those lines from the configuration. Damn you, macOS.&lt;/p&gt;</content:encoded></item><item><title>[Edit: Do NOT use] Setting up Time Machine on any SMB server (feat. automount)</title><link>https://ericswpark.com/blog/2023/2023-01-08-setting-up-time-machine-on-any-smb-server-feat-automount/</link><guid isPermaLink="true">https://ericswpark.com/blog/2023/2023-01-08-setting-up-time-machine-on-any-smb-server-feat-automount/</guid><pubDate>Sun, 08 Jan 2023 13:36:19 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;Update at the end of this blog post!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Time Machine on macOS does a lot of things just right. It’s incremental, so you save disk space on the destination. It runs seamlessly in the background, and if you enable Power Nap it will run even when the lid is closed.&lt;/p&gt;
&lt;p&gt;What it is absolutely terrible at is backing up to anything other than a directly-attached storage device.&lt;/p&gt;
&lt;p&gt;Want to back up to a remote storage? Great, choose from one of the following options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;del&gt;Buy a readymade device from Apple that does this for you&lt;/del&gt; Nope! That was the AirPort lineup and Apple discontinued them centuries ago.&lt;/li&gt;
&lt;li&gt;&lt;del&gt;Back up to a NAS that supports AFP&lt;/del&gt; Nope! AFP is also gone. If your NAS advertised Time Machine support based on AFP, you have a terribly outdated NAS and should get rid of it yesterday.&lt;/li&gt;
&lt;li&gt;Buy a NAS that has SMB and hope that it supports the “Apple extensions” (we’ll get to that in a second) so that Time Machine will actually work. (And chuck the NAS out the window when you discover that it won’t work reliably because the NAS manufacturer did a terrible job configuring Samba.)&lt;/li&gt;
&lt;li&gt;Spend days and days wrestling with the configuration of your DIY NAS in order to get it to work. I tried this. This is not pretty. You will regret this route. (Oh, and it involves the “Apple extensions” I talked about earlier. What are those? Coming up soon!)&lt;/li&gt;
&lt;li&gt;Throw it all out and work around it on your Mac.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Okay, we’ll start from the second one and work our way down.&lt;/p&gt;
&lt;h1 id=&quot;afp&quot;&gt;AFP?&lt;/h1&gt;
&lt;p&gt;AFP stands for Apple Filing Protocol, and I won’t get into it much because it is older than dirt. It’s also gone in the latest versions of macOS, by the way, so you won’t be able to use it for Time Machine, even if you really wanted to force it.&lt;/p&gt;
&lt;h1 id=&quot;apple-extensions&quot;&gt;”Apple extensions?”&lt;/h1&gt;
&lt;p&gt;Apple just had to tack on something to the SMB protocol developed by Microsoft. So they added these “extensions” that make SMB servers work well on Apple products.&lt;/p&gt;
&lt;p&gt;The open-source SMB project, Samba, attempts to implement these in &lt;a href=&quot;https://www.mankier.com/8/vfs_fruit&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;the &lt;code&gt;vfs_fruit&lt;/code&gt; module&lt;/a&gt;, but I’ve had instances where Time Machine would just NOT play nice, even when the module was enabled.&lt;/p&gt;
&lt;p&gt;If Time Machine suddenly stopped working on your “Time Machine-compatible” NAS one day, it’s probably because macOS is chucking a fit because it didn’t get the special &lt;code&gt;vfs_fruit&lt;/code&gt; data that it likes, in &lt;em&gt;just&lt;/em&gt; the right way it wants it.&lt;/p&gt;
&lt;h1 id=&quot;diy-nas&quot;&gt;DIY NAS?&lt;/h1&gt;
&lt;p&gt;So what if you have a DIY NAS, like something running unRAID and OpenMediaVault instead of Synology and QNAP, and attempt to build a Time Machine server off of it?&lt;/p&gt;
&lt;p&gt;Well, a lot of headache. I tried setting up Time Machine with my unRAID NAS. While in theory it was supposed to work OOTB, the backup kept failing sporadically and family members reported the same as well. I spent weeks fiddling around with the configuration but to no avail. Sometimes, it would suddenly work without any issues, then stop working again the next hour, even when I hadn’t changed anything.&lt;/p&gt;
&lt;p&gt;It was maddening and I don’t recommend anybody try this route.&lt;/p&gt;
&lt;h1 id=&quot;the-mac-workaround&quot;&gt;The Mac workaround&lt;/h1&gt;
&lt;p&gt;So here is the method I settled on, and it has worked rather flawlessly for the past couple of days:&lt;/p&gt;
&lt;p&gt;Make your Mac back up to an image stored on the SMB server.&lt;/p&gt;
&lt;p&gt;So I’m going to show you the steps to achieve just that.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Connect to your server.&lt;/li&gt;
&lt;li&gt;Open up Disk Utility and press Command-N to create a new image. Set the options:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Name: “Time Machine”&lt;/li&gt;
&lt;li&gt;Size: Whatever you want, but I recommend making it bigger than your Mac’s drive space&lt;/li&gt;
&lt;li&gt;Format: “APFS”&lt;/li&gt;
&lt;li&gt;Encryption: “128-bit AES encryption (recommended)” (You will be prompted for a password)&lt;/li&gt;
&lt;li&gt;Partitions: “Single partition - GUID Partition Map”&lt;/li&gt;
&lt;li&gt;Image Format: “sparse bundle disk image”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt=&quot;disk-utility-image-creation&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1290&quot; height=&quot;940&quot; src=&quot;/_astro/disk-utility-image-creation.BIQqA2t__Z1lmvxc.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;It is important that you save this locally, like in the “Downloads” folder! Attempting to save this directly to the server will result in the following error:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Operation failed with status 73: RPC version wrong&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can choose whatever you want for the image name (I’ll just call the sparse bundle an image for brevity), but I recommend setting it to the name of your Mac so that you can distinguish the images.&lt;/p&gt;
&lt;p&gt;Once it is created, macOS will automatically mount it. Eject it, then move the file over to your server.&lt;/p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Mount the image. Make sure to click on the checkbox that says “Remember password in my keychain”. This is important if you want to auto-mount the image later.&lt;/li&gt;
&lt;li&gt;Give the “Full Disk Access” permission to your terminal app of choice.&lt;/li&gt;
&lt;li&gt;In your terminal app, run:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;sudo tmutil setdestination /Volumes/Time\ Machine&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Make sure to replace &lt;code&gt;/Volumes/Time\ Machine&lt;/code&gt; with your actual volume name!&lt;/strong&gt; If you’re unsure, delete it, then drag the volume (NOT the image) to the terminal window.&lt;/p&gt;
&lt;p&gt;Once you run it and enter your password, the volume will be set as the Time Machine destination. Your desktop may flash a couple of times. This is normal.&lt;/p&gt;
&lt;ol start=&quot;6&quot;&gt;
&lt;li&gt;Set up automatic backups in Time Machine.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;Note: in some cases, Time Machine may refuse to let you change the schedule to automatic. In this case, quit the “System Settings” app completely, then go back into the Time Machine settings page.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Select “Options”, and change “Back up frequency” to “Automatically every hour”:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;time-machine-automatic-schedule&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1002&quot; height=&quot;698&quot; src=&quot;/_astro/time-machine-automatic-schedule.DjcoSesO_Zz43tR.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;You should probably not turn on “Back up on battery power” unless you really need hourly backups, no matter what.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: If the setting keeps reverting back to “Manually”, try step 7 first, then come back here.&lt;/p&gt;
&lt;ol start=&quot;7&quot;&gt;
&lt;li&gt;Run a manual backup.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Go into the Time Machine settings, right click on the destination, and select “Back up to ‘Time Machine’ now”:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;time-machine-manual-backup&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1054&quot; height=&quot;654&quot; src=&quot;/_astro/time-machine-manual-backup.DWbsC0km_1dBl9Y.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;If your disk showed up as “0 KB available”, this step should fix that.&lt;/p&gt;
&lt;ol start=&quot;8&quot;&gt;
&lt;li&gt;(optional, but recommended) Set up auto-mount.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I ended up using a paid app called &lt;a href=&quot;https://apps.apple.com/kr/app/automounter/id1160435653?l=en&amp;#x26;mt=12&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;“AutoMounter” on the App Store&lt;/a&gt;, but I am pretty sure that there are other options out there.&lt;/p&gt;
&lt;p&gt;Add your SMB share to the app, then add this script to run whenever the share is mounted:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#!/usr/bin/env bash&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Eject previous Time Machine volume if it exists&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;diskutil&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; eject&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /Volumes/Time&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\ &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Machine&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Sleep 10 seconds because macOS is janky&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sleep&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Open disk image file&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;open&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /Volumes/smb-share/TimeMachine/Erics-MacBook-Pro.sparsebundle&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make sure to replace the paths with your own.&lt;/p&gt;
&lt;p&gt;Now, Time Machine will back up whenever it sees the volume mounted, and AutoMounter will mount the volume whenever the SMB share is available!&lt;/p&gt;
&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;I can’t believe using Time Machine is this user-unfriendly. Ideally there should be a button in the Time Machine settings that does all of the above for us. But for now, this will have to do.&lt;/p&gt;
&lt;h1 id=&quot;update-2023-01-26&quot;&gt;Update (2023-01-26)&lt;/h1&gt;
&lt;p&gt;Don’t use this method. When Time Machine backs up to a sparse bundle hosted on a network share, it thrashes the network quite heavily, slowing everything down. I think it has something to do with packages actually being directories containing lots and lots of small files. Because Time Machine can’t get the metadata streamed through with Apple’s SMB extensions, it resorts to fetching the entire file, which slows things down.&lt;/p&gt;
&lt;p&gt;At this point, I’ve given up completely with network-based Time Machine backups. Use an external drive: it’s idiot-proof and easy to restore from.&lt;/p&gt;</content:encoded></item><item><title>Hello, Mastodon!</title><link>https://ericswpark.com/blog/2022/2022-12-16-hello-mastodon/</link><guid isPermaLink="true">https://ericswpark.com/blog/2022/2022-12-16-hello-mastodon/</guid><pubDate>Fri, 16 Dec 2022 08:12:55 GMT</pubDate><content:encoded>&lt;p&gt;After &lt;a href=&quot;/blog/2022/2022-11-26-goodbye-twitter&quot;&gt;deleting my Twitter account&lt;/a&gt;, I’ve been doing some research about Mastodon and how it works. Suffice to say, I now have a Mastodon account over at &lt;a href=&quot;https://tilde.zone/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;tilde.zone&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;My handle is: &lt;a href=&quot;https://tilde.zone/@ericswpark&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;@ericswpark@tilde.zone&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Quick tip for those joining: I was stuck on the choosing-a-server step for a very long time, until I realized account creation is a bit like creating an email account. You don’t really care whether someone has an email at Gmail, Yahoo, or their own self-hosted server. So just pick a server that has a content moderation policy and ideology you most agree/identify with and sign up there! And thanks to federation, you can always contact users on other servers, so don’t worry if you can’t sign up on a particular server.&lt;/p&gt;</content:encoded></item><item><title>ffmpeg: convert HDR to SDR</title><link>https://ericswpark.com/blog/2022/2022-12-14-ffmpeg-convert-hdr-to-sdr/</link><guid isPermaLink="true">https://ericswpark.com/blog/2022/2022-12-14-ffmpeg-convert-hdr-to-sdr/</guid><pubDate>Wed, 14 Dec 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;(Warning to mobile users: this post has a lot of images with huge file sizes, so it may be slow to load. I considered compressing/resizing the images but I wanted the details to be preserved so I left them as-is. If the images don’t load, try viewing this post on a computer. Thanks!)&lt;/p&gt;
&lt;p&gt;If you have a large media collection like me, you may have some content, like movies and videos, that are shot or rendered in &lt;a href=&quot;https://en.wikipedia.org/wiki/High_dynamic_range&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;HDR (High Dynamic Range)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you also have a large collection of devices, you’ll know that most recent devices play back HDR content just fine. Just for a quick reference, here are some popular device lineups and when they started supporting HDR:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Apple
&lt;ul&gt;
&lt;li&gt;iPhone 8/8 Plus and up&lt;sup&gt;&lt;a href=&quot;#user-content-fn-1&quot; id=&quot;user-content-fnref-1&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;iPad 9th-generation and up&lt;/li&gt;
&lt;li&gt;iPad Air 4th-generation and up&lt;/li&gt;
&lt;li&gt;iPad Mini 6th-generation and up&lt;/li&gt;
&lt;li&gt;All iPad Pro models (except the 1st-generation 12.9-inch)&lt;/li&gt;
&lt;li&gt;Macs with Apple Silicon&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Samsung
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://commercialmarineexpo.com/what-is-hdr-support-in-samsung-phone/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Galaxy S10/S10+/S10e and up&lt;/a&gt;&lt;sup&gt;&lt;a href=&quot;#user-content-fn-2&quot; id=&quot;user-content-fnref-2&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://commercialmarineexpo.com/what-is-hdr-support-in-samsung-phone/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Galaxy Note 10/10+ and up&lt;/a&gt;&lt;sup&gt;&lt;a href=&quot;#user-content-fn-2&quot; id=&quot;user-content-fnref-2-2&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In addition, on devices that do not support HDR playback, some platforms and video players (such as VLC) allow you to “tone-map” HDR videos (we’ll get into what that is later) down to SDR, or Standard Dynamic Range. Which means you’ll still be able to enjoy your HDR content for now without upgrading your hardware.&lt;/p&gt;
&lt;p&gt;Unfortunately, you start running into issues when you try and play back HDR content on devices that absolutely do not support HDR content in any way or form. Devices like old Android phones or tablets, or old Apple devices, etc. that do not allow you to load an external player that support HDR tone-mapping, will still allow you to play back the content, but you will notice something wrong about the video. The colors will all be washed out, and the brightness levels will be off. So what gives?&lt;/p&gt;
&lt;h1 id=&quot;color-clipping&quot;&gt;Color-clipping&lt;/h1&gt;
&lt;p&gt;To begin, let’s look at the “color space” that SDR videos use. SDR videos typically use the Rec. 709 color space, which looks a little something like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://upload.wikimedia.org/wikipedia/commons/e/ef/CIExy1931_Rec_709.svg&quot; alt=&quot;rec-709-diagram&quot;/&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt; From &lt;a href=&quot;https://en.wikipedia.org/wiki/Rec._709&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://en.wikipedia.org/wiki/Rec._709&lt;/a&gt; &amp;gt;&lt;/p&gt;
&lt;p&gt;In contrast, HDR videos use the Rec. 2020 color space, which looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://upload.wikimedia.org/wikipedia/commons/b/b6/CIExy1931_Rec_2020.svg&quot; alt=&quot;rec-2020-diagram&quot;/&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt; From &lt;a href=&quot;https://en.wikipedia.org/wiki/Rec._2020&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://en.wikipedia.org/wiki/Rec._2020&lt;/a&gt; &amp;gt;&lt;/p&gt;
&lt;p&gt;Inside the cone shapes, you can see the black triangle that marks what part of the color spectrum the color space can represent. As clearly seen above, Rec. 2020 covers even more colors than Rec. 709.&lt;/p&gt;
&lt;p&gt;This becomes a problem, because to properly display Rec. 2020 colors, your hardware (your device and your screen), platform, and video player must support displaying Rec. 2020 (obviously). For combinations of hardware-platform-software where proper tone-mapping is possible, you will see the correctly mapped color output where colors outside the Rec. 709’s color space has been “mapped” back in to the supported regions (that’s basically what tone-mapping is here).&lt;/p&gt;
&lt;p&gt;But if your playback combo (hardware-platform-software) does not have proper tone-mapping support, the colors outside of the range of Rec. 709 are forcibly shifted in to the supported range (or “clipped”) during playback, and as a result, you get a grayish, washed-out image that looks terrible.&lt;/p&gt;
&lt;p&gt;So how do you solve this problem?&lt;/p&gt;
&lt;h1 id=&quot;solutions&quot;&gt;Solutions&lt;/h1&gt;
&lt;p&gt;Here are some of the solutions, in the most preferable order:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Upgrade your hardware to a HDR-supported device&lt;/li&gt;
&lt;li&gt;Use video players that support tone-mapping on-the-fly&lt;/li&gt;
&lt;li&gt;Obtain SDR versions of the content if it is available&lt;/li&gt;
&lt;li&gt;Re-encode your videos with &lt;code&gt;ffmpeg&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In an ideal world, we’ll all have HDR-capable hardware, but this is not a utopia and there are devices that are being released &lt;em&gt;right now&lt;/em&gt; that do not have proper support (or any support whatsoever) for HDR. And also, even if you only pick out HDR-compatible devices, it will be expensive, and it’s no reason to immediately replace your old devices when alternatives exist. (To be clear: if you were about to upgrade anyway and HDR was one of the reasons, buy away. But if you are upgrading simply because your device doesn’t support HDR, keep in mind that most devices right now, especially computers, do not support HDR, so it’s probably not worth it to upgrade just for HDR support alone.)&lt;/p&gt;
&lt;p&gt;The next best solution would be to use a video player that supports tone-mapping, like VLC. This is applicable to desktop OSes where an alternative video player can be sideloaded at any time. But what if you’re using a device where sideloading in HDR support is hard or even impossible?&lt;/p&gt;
&lt;p&gt;You could attempt to get an SDR copy of the content, if one is available. Going this route is probably easier and faster, since downloading will usually beat out encoding in terms of speed. But what if the HDR copy is the only copy?&lt;/p&gt;
&lt;p&gt;That’s where re-encoding comes in!&lt;/p&gt;
&lt;h1 id=&quot;before-we-begin&quot;&gt;Before we begin&lt;/h1&gt;
&lt;p&gt;I’d like to mention really quickly that most of the HDR to SDR conversion stuff that I write about in the next couple of sections is referenced from &lt;a href=&quot;https://stevens.li/guides/video/converting-hdr-to-sdr-with-ffmpeg/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;this now deleted blog post&lt;/a&gt; (it looks like the entire site is gone). You can &lt;a href=&quot;https://web.archive.org/web/20180817195640/https://stevens.li/guides/video/converting-hdr-to-sdr-with-ffmpeg/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;check out the archived version on Wayback Machine here&lt;/a&gt;. Thanks to the original author, Daniel Stevens, for his blog post on the topic!&lt;/p&gt;
&lt;h1 id=&quot;original&quot;&gt;Original&lt;/h1&gt;
&lt;p&gt;For this demonstration, I’ll be using &lt;a href=&quot;https://www.youtube.com/watch?v=mkggXE5e2yk&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;this video from YouTube&lt;/a&gt; to show the different conversion methods of HDR to SDR. (As the video is too big to attach here, I’ll stick with single frames from the middle of the video. You can follow along with the comparisons by using the same commands below!)&lt;/p&gt;
&lt;p&gt;Let’s extract a single HDR frame and see how it looks in Rec. 709, without any tonemapping:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg -ss 00:01:10.871 -i hdr-original.mp4 -vframes 1 original.png&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/_astro/original.CSQvmSQ2_1DHeHu.webp&quot; alt=&quot;original&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;3840&quot; height=&quot;2160&quot;&gt;&lt;/p&gt;
&lt;p&gt;Looks rather washed out, which is what we expect. So what should we run to convert this to SDR properly?&lt;/p&gt;
&lt;h1 id=&quot;tonemap-algorithms&quot;&gt;Tonemap algorithms&lt;/h1&gt;
&lt;p&gt;There are several tonemap algorithms – &lt;code&gt;mobius&lt;/code&gt;, &lt;code&gt;hable&lt;/code&gt;, and &lt;code&gt;reinhard&lt;/code&gt;. Let’s look at each one!&lt;/p&gt;
&lt;h1 id=&quot;mobius&quot;&gt;&lt;code&gt;mobius&lt;/code&gt;&lt;/h1&gt;
&lt;p&gt;Extract the same frame and apply the &lt;code&gt;mobius&lt;/code&gt; tonemap preset:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg -ss 00:01:10.871 -i hdr-original.mp4 -vf &amp;#39;zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=mobius,zscale=t=bt709:m=bt709:r=tv,format=yuv420p&amp;#39; -vframes 1 mobius.png&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And here’s what we end up with:&lt;/p&gt;

&lt;img src=&quot;/_astro/mobius.D7kYdtKw.png&quot; width=&quot;3840&quot; height=&quot;2160&quot; loading=&quot;lazy&quot; alt=&quot;Mobius example&quot;/&gt;
&lt;p&gt;Okay, we see more colors! But something still feels off. The colors look too vibrant.&lt;/p&gt;
&lt;p&gt;Let’s try the other two.&lt;/p&gt;
&lt;h1 id=&quot;hable&quot;&gt;&lt;code&gt;hable&lt;/code&gt;&lt;/h1&gt;
&lt;p&gt;The command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg -ss 00:01:10.871 -i hdr-original.mp4 -vf &amp;#39;zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=hable,zscale=t=bt709:m=bt709:r=tv,format=yuv420p&amp;#39; -vframes 1 hable.png&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The result:&lt;/p&gt;

&lt;img src=&quot;/_astro/hable.BiAmz5eN.png&quot; width=&quot;3840&quot; height=&quot;2160&quot; loading=&quot;lazy&quot; alt=&quot;Hable example&quot;/&gt;
&lt;p&gt;Okay, that looks… more like what we’re aiming for. What about…&lt;/p&gt;
&lt;h1 id=&quot;reinhard&quot;&gt;&lt;code&gt;reinhard&lt;/code&gt;&lt;/h1&gt;
&lt;p&gt;The command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg -ss 00:01:10.871 -i hdr-original.mp4 -vf &amp;#39;zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=reinhard,zscale=t=bt709:m=bt709:r=tv,format=yuv420p&amp;#39; -vframes 1 reinhard.png&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The result:&lt;/p&gt;

&lt;img src=&quot;/_astro/reinhard.BzXjw3de.png&quot; width=&quot;3840&quot; height=&quot;2160&quot; loading=&quot;lazy&quot; alt=&quot;Reinhard example&quot;/&gt;
&lt;p&gt;It might be difficult to pick out the difference between this and &lt;code&gt;hable&lt;/code&gt;, so I’ve made a little comparison thing below:&lt;/p&gt;
&lt;script type=&quot;module&quot; src=&quot;/home/runner/work/ericswpark.github.io/ericswpark.github.io/src/components/embeds/ImgComparison.astro?astro&amp;type=script&amp;index=0&amp;lang.ts&quot;&gt;&lt;/script&gt; &lt;img-comparison-slider&gt; &lt;img slot=&quot;first&quot; src=&quot;/_astro/hable.BiAmz5eN.png&quot; width=&quot;3840&quot; height=&quot;2160&quot; loading=&quot;lazy&quot; alt=&quot;Hable example&quot;&gt; &lt;img slot=&quot;second&quot; src=&quot;/_astro/reinhard.BzXjw3de.png&quot; width=&quot;3840&quot; height=&quot;2160&quot; loading=&quot;lazy&quot; alt=&quot;Reinhard example&quot;&gt; &lt;/img-comparison-slider&gt;
&lt;p&gt;(&lt;code&gt;hable&lt;/code&gt; on left, &lt;code&gt;reinhard&lt;/code&gt; on right)&lt;/p&gt;
&lt;p&gt;And in case you want to see how badly oversaturated &lt;code&gt;mobius&lt;/code&gt; is:&lt;/p&gt;
 &lt;img-comparison-slider&gt; &lt;img slot=&quot;first&quot; src=&quot;/_astro/mobius.D7kYdtKw.png&quot; width=&quot;3840&quot; height=&quot;2160&quot; loading=&quot;lazy&quot; alt=&quot;Mobius example&quot;&gt; &lt;img slot=&quot;second&quot; src=&quot;/_astro/hable.BiAmz5eN.png&quot; width=&quot;3840&quot; height=&quot;2160&quot; loading=&quot;lazy&quot; alt=&quot;Hable example&quot;&gt; &lt;/img-comparison-slider&gt;
&lt;p&gt;(&lt;code&gt;mobius&lt;/code&gt; on left, &lt;code&gt;hable&lt;/code&gt; on right)&lt;/p&gt;
 &lt;img-comparison-slider&gt; &lt;img slot=&quot;first&quot; src=&quot;/_astro/mobius.D7kYdtKw.png&quot; width=&quot;3840&quot; height=&quot;2160&quot; loading=&quot;lazy&quot; alt=&quot;Mobius example&quot;&gt; &lt;img slot=&quot;second&quot; src=&quot;/_astro/reinhard.BzXjw3de.png&quot; width=&quot;3840&quot; height=&quot;2160&quot; loading=&quot;lazy&quot; alt=&quot;Reinhard example&quot;&gt; &lt;/img-comparison-slider&gt;
&lt;p&gt;(&lt;code&gt;mobius&lt;/code&gt; on left, &lt;code&gt;reinhard&lt;/code&gt; on right)&lt;/p&gt;
&lt;p&gt;Now that we’re done with the available presets, it’s time to go over one tweakable value within each preset. And that is…&lt;/p&gt;
&lt;h1 id=&quot;desaturation&quot;&gt;Desaturation&lt;/h1&gt;
&lt;p&gt;From &lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#Options-3&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;ffmpeg&lt;/code&gt;’s documentation&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Apply desaturation for highlights that exceed this level of brightness. The higher the parameter, the more color information will be preserved. This setting helps prevent unnaturally blown-out colors for super-highlights, by (smoothly) turning into white instead. This makes images feel more natural, at the cost of reducing information about out-of-range colors.&lt;/p&gt;
&lt;p&gt;The default of 2.0 is somewhat conservative and will mostly just apply to skies or directly sunlit surfaces. A setting of 0.0 disables this option.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This tracks with the explanation from the blog article from Daniel Stevens. However, he recommends using the value of &lt;code&gt;0&lt;/code&gt; to turn it off. But the documentation makes it sound like it’s a good setting to leave on to prevent “unnaturally blown-out colors for super-highlights.”&lt;/p&gt;
&lt;p&gt;Let’s try generating a &lt;code&gt;mobius&lt;/code&gt; image with desaturation set to &lt;code&gt;0&lt;/code&gt; and compare it with the regular &lt;code&gt;mobius&lt;/code&gt; (again, the defaults are &lt;code&gt;2.0&lt;/code&gt;. If I don’t specify a desaturation value, assume &lt;code&gt;2.0&lt;/code&gt;):&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg -ss 00:01:10.871 -i hdr-original.mp4 -vf &amp;#39;zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=mobius:desat=0,zscale=t=bt709:m=bt709:r=tv,format=yuv420p&amp;#39; -vframes 1 mobius-desat-0.png&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since it doesn’t make sense to just show the image on its own, I’ll make another comparison slider below:&lt;/p&gt;

 &lt;img-comparison-slider&gt; &lt;img slot=&quot;first&quot; src=&quot;/_astro/mobius.D7kYdtKw.png&quot; width=&quot;3840&quot; height=&quot;2160&quot; loading=&quot;lazy&quot; alt=&quot;Mobius example&quot;&gt; &lt;img slot=&quot;second&quot; src=&quot;/_astro/mobius-desat-0.COlVAZMK.png&quot; width=&quot;3840&quot; height=&quot;2160&quot; loading=&quot;lazy&quot; alt=&quot;Mobius with desat 0 example&quot;&gt; &lt;/img-comparison-slider&gt;
&lt;p&gt;(&lt;code&gt;mobius&lt;/code&gt; on left, &lt;code&gt;mobius&lt;/code&gt; with desaturation set to &lt;code&gt;0&lt;/code&gt; on right)&lt;/p&gt;
&lt;p&gt;And the rest:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg -ss 00:01:10.871 -i hdr-original.mp4 -vf &amp;#39;zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=hable:desat=0,zscale=t=bt709:m=bt709:r=tv,format=yuv420p&amp;#39; -vframes 1 hable-desat-0.png&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg -ss 00:01:10.871 -i hdr-original.mp4 -vf &amp;#39;zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=reinhard:desat=0,zscale=t=bt709:m=bt709:r=tv,format=yuv420p&amp;#39; -vframes 1 reinhard-desat-0.png&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

 &lt;img-comparison-slider&gt; &lt;img slot=&quot;first&quot; src=&quot;/_astro/hable.BiAmz5eN.png&quot; width=&quot;3840&quot; height=&quot;2160&quot; loading=&quot;lazy&quot; alt=&quot;Hable example&quot;&gt; &lt;img slot=&quot;second&quot; src=&quot;/_astro/hable-desat-0.Bxjh-wZS.png&quot; width=&quot;3840&quot; height=&quot;2160&quot; loading=&quot;lazy&quot; alt=&quot;Hable with desat 0 example&quot;&gt; &lt;/img-comparison-slider&gt;
&lt;p&gt;(&lt;code&gt;hable&lt;/code&gt; on left, &lt;code&gt;hable&lt;/code&gt; with desaturation set to &lt;code&gt;0&lt;/code&gt; on right)&lt;/p&gt;

 &lt;img-comparison-slider&gt; &lt;img slot=&quot;first&quot; src=&quot;/_astro/reinhard.BzXjw3de.png&quot; width=&quot;3840&quot; height=&quot;2160&quot; loading=&quot;lazy&quot; alt=&quot;Reinhard example&quot;&gt; &lt;img slot=&quot;second&quot; src=&quot;/_astro/reinhard-desat-0.DvIc95Rv.png&quot; width=&quot;3840&quot; height=&quot;2160&quot; loading=&quot;lazy&quot; alt=&quot;Reinhard with desat 0 example&quot;&gt; &lt;/img-comparison-slider&gt;
&lt;p&gt;(&lt;code&gt;reinhard&lt;/code&gt; on left, &lt;code&gt;reinhard&lt;/code&gt; with desaturation set to &lt;code&gt;0&lt;/code&gt; on right)&lt;/p&gt;
&lt;p&gt;Let’s try a different value. I’ll just generate a &lt;code&gt;0.5&lt;/code&gt; for &lt;code&gt;reinhard&lt;/code&gt;, though you can substitute whatever you want for different presets. And here’s the comparison:&lt;/p&gt;

 &lt;img-comparison-slider&gt; &lt;img slot=&quot;first&quot; src=&quot;/_astro/reinhard-desat-0.5.DozorF28.png&quot; width=&quot;3840&quot; height=&quot;2160&quot; loading=&quot;lazy&quot; alt=&quot;Reinhard with desat 0.5 example&quot;&gt; &lt;img slot=&quot;second&quot; src=&quot;/_astro/reinhard.BzXjw3de.png&quot; width=&quot;3840&quot; height=&quot;2160&quot; loading=&quot;lazy&quot; alt=&quot;Reinhard example&quot;&gt; &lt;/img-comparison-slider&gt;
&lt;p&gt;(&lt;code&gt;reinhard&lt;/code&gt; with desaturation set to &lt;code&gt;0.5&lt;/code&gt; on left, &lt;code&gt;reinhard&lt;/code&gt; on right)&lt;/p&gt;
&lt;p&gt;I think this might be a matter of personal preference. If someone can tell me exactly why this should be disabled, I will update this section.&lt;/p&gt;
&lt;p&gt;So, which one should you use to convert HDR videos into SDR?&lt;/p&gt;
&lt;h1 id=&quot;personal-comparisons-and-thoughts&quot;&gt;(Personal) comparisons and thoughts&lt;/h1&gt;
&lt;p&gt;These are my personal thoughts based on the experiments I carried out above.&lt;/p&gt;
&lt;p&gt;I immediately ruled out &lt;code&gt;mobius&lt;/code&gt; because the algorithm distorts the colors and oversaturates them a lot. If you like that kind of punchy color, then it might be a good option, but I feel like it messes up the video’s color too much to be usable.&lt;/p&gt;
&lt;p&gt;That leaves &lt;code&gt;hable&lt;/code&gt; and &lt;code&gt;reinhard&lt;/code&gt;. I found that &lt;code&gt;reinhard&lt;/code&gt; produced brighter images, but &lt;code&gt;hable&lt;/code&gt; looked more in-line with the original SDR source (when using content where HDR and SDR were both available so I could compare the output from &lt;code&gt;ffmpeg&lt;/code&gt;). I think it’s a matter of personal preference, again, but I think I’ll go with &lt;code&gt;reinhard&lt;/code&gt;, as I value being able to see things in a dark scene, which is harder to do with &lt;code&gt;hable&lt;/code&gt;.&lt;/p&gt;
&lt;h1 id=&quot;usage-examples&quot;&gt;Usage examples&lt;/h1&gt;
&lt;p&gt;I wondered what algorithms were being used by various software that implemented HDR to SDR tonemapping and found the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A lot of implementations and code examples on Stack Overflow preferred using &lt;code&gt;hable&lt;/code&gt; with desaturation set to &lt;code&gt;0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;On Jellyfin, &lt;a href=&quot;https://github.com/jellyfin/jellyfin/issues/415&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;the original issue thread about HDR to SDR tonemapping&lt;/a&gt; had a lot of commenters saying that they used &lt;code&gt;hable&lt;/code&gt; with desaturation set to &lt;code&gt;0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;On Jellyfin, &lt;a href=&quot;https://github.com/jellyfin/jellyfin/pull/3442&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;the pull request that implemented tonemapping&lt;/a&gt; had &lt;code&gt;reinhard&lt;/code&gt; as the default value, with a recommended &lt;code&gt;desat&lt;/code&gt; range between &lt;code&gt;0&lt;/code&gt; and &lt;code&gt;0.5&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Inside the aforementioned pull request, &lt;a href=&quot;https://github.com/jellyfin/jellyfin/pull/3442/files#r465143195&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;one commenter asked why &lt;code&gt;hable&lt;/code&gt; was not being used&lt;/a&gt;, and a Jellyfin developer (I assume) stated that &lt;a href=&quot;https://github.com/jellyfin/jellyfin/pull/3442/files#r467430384&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;hable&lt;/code&gt; wasn’t producing enough brightness as &lt;code&gt;reinhard&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;converting-actual-video&quot;&gt;Converting actual video&lt;/h1&gt;
&lt;p&gt;So once you have your preferred algorithm, converting an entire video is as simple as removing the &lt;code&gt;-ss&lt;/code&gt; and &lt;code&gt;-vframes&lt;/code&gt; arguments and adding parameters for the used encoder, etc. like so:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg -i input.mp4 -vf &amp;#39;zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=reinhard,zscale=t=bt709:m=bt709:r=tv,format=yuv420p&amp;#39; -c:v libx264 -preset veryfast -crf 18 -c:a aac -b:a 160k -movflags +faststart output.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;While there is no one-size-fits-all solution for HDR-to-SDR tonemapping, the experiments above and the explanations should give you a pretty good understanding of how the process works and what you should test out to figure out your preferred algorithm.&lt;/p&gt;
&lt;section data-footnotes class=&quot;footnotes&quot;&gt;&lt;h2 class=&quot;sr-only&quot; id=&quot;footnote-label&quot;&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li id=&quot;user-content-fn-1&quot;&gt;
&lt;p&gt;While all iPhones above 8 and 8 Plus support HDR playback, that’s only on a software level. &lt;a href=&quot;https://mashable.com/article/netflix-hdr-not-real-iphone-8-plus&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Some iPhones (including the aforementioned 8 and 8 Plus and probably some “budget” tier models like iPhone SE) do not support displaying actual HDR on a hardware level&lt;/a&gt;. &lt;a href=&quot;#user-content-fnref-1&quot; data-footnote-backref aria-label=&quot;Back to reference 1&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-2&quot;&gt;
&lt;p&gt;Technically, Samsung supported “mobile HDR” playback starting from Galaxy S8/S8+ and Note 8. However, support for HDR10+ was only added starting with the devices listed above. &lt;a href=&quot;#user-content-fnref-2&quot; data-footnote-backref aria-label=&quot;Back to reference 2&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-2-2&quot; data-footnote-backref aria-label=&quot;Back to reference 2-2&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</content:encoded></item><item><title>ffmpeg: cut videos to the exact frame</title><link>https://ericswpark.com/blog/2022/2022-12-03-ffmpeg-cut-videos-to-the-exact-frame/</link><guid isPermaLink="true">https://ericswpark.com/blog/2022/2022-12-03-ffmpeg-cut-videos-to-the-exact-frame/</guid><pubDate>Sat, 03 Dec 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When cutting videos using &lt;code&gt;ffmpeg&lt;/code&gt; using the following command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg -i input.mp4 -ss 00:00:24.183 -to 00:03:22.183 -c copy output.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You may notice that &lt;code&gt;ffmpeg&lt;/code&gt; may sometimes not cut the video to the correct frame. It may start a bit later than the time specified, &lt;code&gt;00:00:24.183&lt;/code&gt;, by a couple of frames or a couple of seconds. Worse yet, the video may appear frozen until it reaches the arbitrary frame &lt;code&gt;ffmpeg&lt;/code&gt; started on, upon which it will start playing normally. What gives?&lt;/p&gt;
&lt;p&gt;Well, it all comes down to keyframe types (again, if you’ve seen my previous posts about &lt;code&gt;ffmpeg&lt;/code&gt;), but the short gist of it is that certain keyframes (P-frames and B-frames) rely on I-frames in order to render. So if your supplied timecode does not start on a I-frame, &lt;code&gt;ffmpeg&lt;/code&gt; skips over until it finds the first I-frame, then repeats that to the start of the video, like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Original file:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;IPPPBIPPPBIPPPBIPPPB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;       ^ Your supplied start timecode&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Where ffmpeg will start:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;----------IPPPBIPPPB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          ^&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;What ffmpeg will do for the missing frames:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-------IIIIPPPBIPPPB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Finally cut down to size:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;IIIIPPPBIPPPB&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So how do we tell &lt;code&gt;ffmpeg&lt;/code&gt; to just re-encode based on the previous I-frame, before your start timecode? Well, do you see the &lt;code&gt;-c copy&lt;/code&gt; parameter in the original command? That’s what’s causing the issue. The parameter tells &lt;code&gt;ffmpeg&lt;/code&gt; to copy the file without re-encoding anything, which is fast, but it won’t let &lt;code&gt;ffmpeg&lt;/code&gt; re-encode by rendering the frames on top of each other. The solution is to just replace that parameter with a instruction to re-encode with an encoder:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg -i input.mp4 -ss 00:00:24.183 -to 00:03:22.183 -c:v libx264 -crf 18 output.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title>Goodbye, Twitter</title><link>https://ericswpark.com/blog/2022/2022-11-26-goodbye-twitter/</link><guid isPermaLink="true">https://ericswpark.com/blog/2022/2022-11-26-goodbye-twitter/</guid><pubDate>Sat, 26 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;By the time you’re reading this post, &lt;a href=&quot;https://twitter.com/ericswpark&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;my Twitter account with the handle of @ericswpark&lt;/a&gt; will be gone. If you’re reading this way in the future, it might have been snatched up by a bot.&lt;/p&gt;
&lt;p&gt;I don’t think I’ll have to worry about impersonation because I’m not famous enough. But just in case: I do not and will not have a Twitter account. Ignore any communications claiming to be me on Twitter.&lt;/p&gt;
&lt;p&gt;The official methods of contact can be found by clicking on the “About” section in the navigation bar.&lt;/p&gt;
&lt;p&gt;Anyway, noticed that I’ve had my handle on Twitter since July 2011. Goodbye, Twitter.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;twitter-profile&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1202&quot; height=&quot;630&quot; src=&quot;/_astro/twitter-profile-en.BHmzS8Ro_27JSF2.webp&quot; &gt;&lt;/p&gt;
&lt;h2 id=&quot;update-2022-12-16&quot;&gt;Update (2022-12-16)&lt;/h2&gt;
&lt;p&gt;Apparently &lt;a href=&quot;https://reddit.com/r/RealTwitterAccounts/comments/zn4lnc/twitter_are_now_refusing_to_delete_suspended/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Twitter has started preventing account deletions&lt;/a&gt;. If you haven’t gotten off the train yet, now would be a good time to (attempt to) do so.&lt;/p&gt;</content:encoded></item><item><title>Smooth scrubbing videos</title><link>https://ericswpark.com/blog/2022/2022-11-07-smooth-scrubbing-videos/</link><guid isPermaLink="true">https://ericswpark.com/blog/2022/2022-11-07-smooth-scrubbing-videos/</guid><pubDate>Mon, 07 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h1 id=&quot;update&quot;&gt;Update&lt;/h1&gt;
&lt;p&gt;The &lt;a href=&quot;/blog/2023/2023-08-07-smooth-scrubbing-videos-version-2&quot;&gt;new revision of this blog post can be found here.&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;You may have noticed that some videos you play in video players allow you to “scrub” smoothly. That is, when you “scrub” the video by moving the playhead back and forth, the frame is instantly shown on screen without any stuttering or “jank,” as long as your playback device can keep up, of course.&lt;/p&gt;
&lt;p&gt;Not only does the player scrub frame-by-frame, some videos even let you scrub backwards, too. Which is great if you’re analyzing a replay or trying to find a specific moment in a video.&lt;/p&gt;
&lt;p&gt;Well, in this blog post, I’ll explain how some videos manage to scrub so well, and how you can re-encode videos so that they scrub perfectly!&lt;/p&gt;
&lt;h1 id=&quot;a-quick-overview-on-video-encoding-and-decoding&quot;&gt;A quick overview on video encoding and decoding&lt;/h1&gt;
&lt;p&gt;&lt;em&gt;Note: this is a simplified version of how video encoders and decoders work. For the purposes of this blog post, we don’t need to completely understand them, but we still need to know what keyframe types are so that the next part makes sense. If any experts are wincing at the parts below I’m sorry!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;As we all know, video files are just containers containing one or more video streams and one or more audio streams, as well as other stream types (like subtitles, etc). We’ll ignore audio streams and other stream types for now, because they’re not important. What we’ll focus on is the video stream.&lt;/p&gt;
&lt;p&gt;A video stream is just a collection of photos, or frames of photos. But storing photos as just one raw frame is rather large and wasteful. Think about it.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A pixel is made up of red, green, and blue values (RGB), each from 0 to 255 (we’ll assume that the video is encoded in 8-bit color here).&lt;/li&gt;
&lt;li&gt;That means there are three color types, each with 8 bits of information. 8 bit is 1 byte, so we have 3 bytes of data per pixel to store.&lt;/li&gt;
&lt;li&gt;For a given 1080p video file, there will be 1920 * 1080 pixels to store. 1920 * 1080 = 2,073,600. Each pixel has 3 bytes, so we’re up to 6,220,800 bytes for a single frame. (That’s 6,221 kilobytes, or 6.2 megabytes of data.)&lt;/li&gt;
&lt;li&gt;Let’s say the video file has 24 frames per second (typical for movies and what not). 6,220,800 * 24 = 149,299,200 bytes, or 149,299 kilobytes, or 149 megabytes. For a single second of video!&lt;/li&gt;
&lt;li&gt;Let’s say it’s a short video file, about 30 seconds long. 149,299,200 * 30 = 4,478,976,000 bytes, or 4,478,976 kilobytes, or 4,479 megabytes, or 4.4 gigabytes.&lt;/li&gt;
&lt;li&gt;60 seconds. 149,299,200 * 60 = 8,957,952,000 bytes = 8,957,952 kilobytes = 8,958 megabytes = 9 gigabytes!&lt;/li&gt;
&lt;li&gt;How about a two-hour long movie? 149,299,200 * 60 * 60 * 2 = 1,074,954,240,000 bytes = 1,074,952,240 kilobytes = 1,074,952 megabytes = 1,075 gigabytes = 1 terabyte.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Obviously, you don’t want to store that much data. And that’s where video encoders and decoders come in.&lt;/p&gt;
&lt;p&gt;Video streams are encoded and decoded by various standards: H264 being one, with H265 and AV1 competing to replace it. (There are more, of course.) A video encoder’s job is to make keyframes out of the frames of video. There are three types of keyframes: I-frames, P-frames, and B-frames.&lt;/p&gt;
&lt;p&gt;I-frames are like the frames we talked about before. They contain all of the data for a single frame of video.&lt;/p&gt;
&lt;p&gt;P-frames reference I-frames. Most video content have some sort of pattern (for example, a static background with a moving subject in front). In this case, the video encoder can just make an I-frame containing the entire scene, then make a P-frame that says “hey, the subject in the video moved right by two thousand pixels and moved up by four hundred pixels.” Then the decoder uses that data to just re-draw the changed parts on top of the I-frame.&lt;/p&gt;
&lt;p&gt;B-frames reference frames forwards and backwards. They save space the most, at the cost of being hard to encode and decode.&lt;/p&gt;
&lt;p&gt;So that’s a very brief and dumbed-down overview on video encoding and decoding. So how does that relate to scrubbing, anyway?&lt;/p&gt;
&lt;h1 id=&quot;keyframes-and-scrubbing&quot;&gt;Keyframes and scrubbing&lt;/h1&gt;
&lt;p&gt;When you scrub back and forth in a video, the player must first find the closest I-frame to the playhead. Then it builds up the P-frames and B-frames on top of the I-frames until you get the exact picture you want. (Some players skip this and just forcibly land you on the nearest I-frame. You may have experienced this if your playhead jumps back or forward a bit from where you let go of it.)&lt;/p&gt;
&lt;p&gt;This is because P-frames and B-frames are partial frames. You can’t just display them because they describe how the frame has changed relative to the I-frame preceding them. (Like I said before, if I-frames are instructions like “draw a blue background sky with white clouds and a person wearing a green dress in front”, P and B-frames are like “the green dress moved right by two hundred pixels.” You can’t draw P and B-frames without reading the I-frame instruction, and so can’t a computer.)&lt;/p&gt;
&lt;p&gt;Now, devices (modern ones, anyway) can decode I-frames and P-frames relatively fast. So if you scrub through a video that is made up of I and P-frames only, it will be smooth. But B-frames take much more time and compute power to process, which means devices may start to lag and jank around when you scrub fast across the timeline.&lt;/p&gt;
&lt;p&gt;The solution?&lt;/p&gt;
&lt;h1 id=&quot;be-gone-b-frames&quot;&gt;Be gone, B-frames!&lt;/h1&gt;
&lt;p&gt;You can re-encode videos to remove all B-frames from them! Here’s a quick &lt;code&gt;ffmpeg&lt;/code&gt; command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg -i input.mp4 -bf 0 output.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that depending on the encoder used, you may need to type something different, like:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg -i input.mp4 -c:v libx264 -x264-params bframes=0 output.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;videos-that-scrub-smoothly&quot;&gt;Videos that scrub smoothly&lt;/h1&gt;
&lt;p&gt;If you want to try a video that scrubs smoothly, and if you own a Nintendo Switch, you’re in luck. Gameplay footage saved on a Nintendo Switch are comprised completely of I and P-frames. Just grab one and try scrubbing through it! This is a quick and easy example to check if you can’t be bothered with re-encoding video.&lt;/p&gt;</content:encoded></item><item><title>Please stop using the stale bot incorrectly</title><link>https://ericswpark.com/blog/2022/2022-10-21-please-stop-using-the-stale-bot-incorrectly/</link><guid isPermaLink="true">https://ericswpark.com/blog/2022/2022-10-21-please-stop-using-the-stale-bot-incorrectly/</guid><pubDate>Fri, 21 Oct 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A lot of projects on GitHub use the “GitHub Stale Bot” to automatically close issues (and pull requests) that have not been updated in a while. The bot automatically goes through the issues and leaves a comment asking if the issue is still relevant, then auto-closes the issue if no comment reply is left within a certain time period configured by the repository owner.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This is absolutely terrible, both for the user and also the developer.&lt;/strong&gt;&lt;/p&gt;
&lt;h1 id=&quot;old-issues-are-buried&quot;&gt;Old issues are buried&lt;/h1&gt;
&lt;p&gt;Issues are not out of sight, out of mind – even if you close them off with the stale bot that doesn’t mean your project has one less bug.&lt;/p&gt;
&lt;p&gt;In fact, it’s even worse for your project, because now all of the relevant information regarding the bug has been buried into the “Closed” section of GitHub issues. People can’t find the existing issue, so what do they do? Create a new issue. Which means you need to:&lt;/p&gt;
&lt;p&gt;a) mark the new issue as a duplicate and re-open the old one, or&lt;/p&gt;
&lt;p&gt;b) find some way of merging the issue chains, or&lt;/p&gt;
&lt;p&gt;c) upset your users by having them copy and paste all the relevant information from the old issue&lt;/p&gt;
&lt;p&gt;Which leads to…&lt;/p&gt;
&lt;h1 id=&quot;more-work&quot;&gt;More work&lt;/h1&gt;
&lt;p&gt;Rather than reducing the burden of the developers, the stale bot ironically creates even more administrative work down the line as issues are buried and duplicate ones are newly filed, doing the &lt;strong&gt;polar opposite&lt;/strong&gt; of what the bot originally set out to achieve. So instead of spending your time writing code, you need to babysit the bot and deal with the backlash when it inevitably pisses off the users of your project.&lt;/p&gt;
&lt;h1 id=&quot;messy-threads&quot;&gt;Messy threads&lt;/h1&gt;
&lt;p&gt;So let’s say users of your project are active and can reply to the bot, indicating that a given issue is still relevant. However, as long as the bot stays active, the back and forth like the following will continue:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;GSB (GitHub Stale Bot): Hey, this issue seems inactive. Close it?&lt;/p&gt;
&lt;p&gt;&lt;em&gt;GSB added the &lt;code&gt;stale&lt;/code&gt; tag.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;User: No, it’s still relevant.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;GSB removed the &lt;code&gt;stale&lt;/code&gt; tag.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(a few moments later)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;GSB: Hey, this issue seems inactive. Close it?&lt;/p&gt;
&lt;p&gt;&lt;em&gt;GSB added the &lt;code&gt;stale&lt;/code&gt; tag.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;User: No… it’s still relevant.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;GSB removed the &lt;code&gt;stale&lt;/code&gt; tag.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Developer: I’m still working on the fix, but we’re blocked on dependency X at the moment.&lt;/p&gt;
&lt;p&gt;GSB: Hey, this issue seems inactive. Close it?&lt;/p&gt;
&lt;p&gt;&lt;em&gt;GSB added the &lt;code&gt;stale&lt;/code&gt; tag.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Developer: kill me&lt;/p&gt;
&lt;p&gt;&lt;em&gt;GSB removed the &lt;code&gt;stale&lt;/code&gt; tag.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Same conversation, without the annoying bot:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Developer: I’m still working on the fix, but we’re blocked on dependency X at the moment.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(a few moments later)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Developer: Okay, upstream fixed their stuff, now we can finally create a patch!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Developer merged &lt;code&gt;feature-fix&lt;/code&gt; branch.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Developer closed as completed.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I know which thread I prefer, both as a developer and as a user.&lt;/p&gt;
&lt;h1 id=&quot;but&quot;&gt;But…&lt;/h1&gt;
&lt;p&gt;Okay, I’ll admit there is one use for the Stale Bot. And that is when you’re awaiting user feedback.&lt;/p&gt;
&lt;p&gt;If the user doesn’t reply in X amount of time (X here depends on your patience), it’s totally fine for the stale bot to drop the issue, because in my opinion it usually means the issue wasn’t that important to them and therefore they didn’t bother with following up with further information, like debugging logs and such.&lt;/p&gt;
&lt;p&gt;So have the bot only monitor issues with a specific tag, like &lt;code&gt;awaiting-user-feedback&lt;/code&gt;, and start the countdown once the tag has been added to an issue.&lt;/p&gt;
&lt;p&gt;This is honestly the only valid use case I found for the Stale Bot, that actually relieves some of the hassle of dealing with “stale” bug reports.&lt;/p&gt;
&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Just because a given issue doesn’t have a reply for a while doesn’t mean it’s stale! Please stop using the stale bot incorrectly. Your project users (and developers) will thank you. :)&lt;/p&gt;</content:encoded></item><item><title>Manually installing packages on UnRAID</title><link>https://ericswpark.com/blog/2022/2022-09-24-manually-installing-packages-on-unraid/</link><guid isPermaLink="true">https://ericswpark.com/blog/2022/2022-09-24-manually-installing-packages-on-unraid/</guid><pubDate>Sat, 24 Sep 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h1 id=&quot;update&quot;&gt;Update&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/UnRAIDES/unRAID-NerdTools&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;UnRAID now has a plugin available named NerdTools&lt;/a&gt;. It is pretty much a continuation of the now-discontinued NerdPack plugin. Check it out! The original article is as follows, but is now outdated.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I noticed today that there was a new update for our home server, UnRAID 6.11.0. I didn’t think much of it as I clicked on “Update,” then rebooted the server. Well, it failed to come back on to the access panel after a reboot, and I panicked for about half an hour trying to get it to come back up.&lt;/p&gt;
&lt;p&gt;Eventually, I figured out what went wrong. Starting from UnRAID 6.11.x, the NerdPack plugin had been deprecated, which meant it was automatically removed from the system during the update. One of the utilities provided by NerdPack was &lt;code&gt;screen&lt;/code&gt; – which, if you don’t know, is a utility that provides persistent terminal sessions (I’m pretty sure &lt;code&gt;screen&lt;/code&gt; has multiple other uses but that’s what I use it for) – and it had been removed alongside NerdPack. Unfortunately, the script that I used to update and run Tailscale (a VPN platform that allows you to link your devices together) relied on &lt;code&gt;screen&lt;/code&gt;, which meant the next time the server rebooted, it couldn’t connect to Tailscale, which is why I couldn’t access it remotely.&lt;/p&gt;
&lt;p&gt;After fixing that problem, I wanted to document how to manually install packages on UnRAID, since the solution is strewn across multiple forum posts and comments and I just want one post I can refer back to when I need to install packages again. I will link all the forum posts and comments I referred to at the bottom of this post.&lt;/p&gt;
&lt;h1 id=&quot;figure-out-unraid-base&quot;&gt;Figure out UnRAID base&lt;/h1&gt;
&lt;p&gt;UnRAID is built on top of Slackware, which means we need to figure out which Slackware version UnRAID is based off of. To do this, run the following command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cat /etc/slackware-version&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It should spit back something like:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Slackware 15.0+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;find-the-package-you-want-to-install&quot;&gt;Find the package you want to install&lt;/h1&gt;
&lt;p&gt;This page lists all the packages available on Slackware 15.0. (If you’re reading this in the future, change the URL based on the underlying Slackware version you found above.)&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://slackware.pkgs.org/15.0/slackware-x86_64/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://slackware.pkgs.org/15.0/slackware-x86_64/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Once you find it, click into the link and find the download URL. For example, the &lt;code&gt;screen&lt;/code&gt; package’s URL was &lt;a href=&quot;https://slackware.pkgs.org/15.0/slackware-x86_64/screen-4.9.0-x86_64-1.txz.html&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://slackware.pkgs.org/15.0/slackware-x86_64/screen-4.9.0-x86_64-1.txz.html&lt;/a&gt;, and once I went inside the download URL was next to the text “Binary Package,” with the following value: &lt;a href=&quot;https://slackware.uk/slackware/slackware64-15.0/slackware64/ap/screen-4.9.0-x86_64-1.txz&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://slackware.uk/slackware/slackware64-15.0/slackware64/ap/screen-4.9.0-x86_64-1.txz&lt;/a&gt; . Copy the URL into your clipboard.&lt;/p&gt;
&lt;h1 id=&quot;download-the-package-to-unraid&quot;&gt;Download the package to UnRAID&lt;/h1&gt;
&lt;p&gt;SSH (or mosh) into your UnRAID server and go into the &lt;code&gt;/boot/extra&lt;/code&gt; directory:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ssh tower&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cd /boot/extra&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The second part is important, because UnRAID automatically installs all packages found within that directory on first boot.&lt;/p&gt;
&lt;p&gt;Then download the package using &lt;code&gt;wget&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;wget &amp;#x3C;package download URL here&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For example, to download &lt;code&gt;screen&lt;/code&gt;, I ran:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;wget https://slackware.uk/slackware/slackware64-15.0/slackware64/ap/screen-4.9.0-x86_64-1.txz&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;start-using-the-package-without-rebooting&quot;&gt;Start using the package without rebooting&lt;/h1&gt;
&lt;p&gt;At this point the package should be available for use if you reboot your UnRAID server. If you don’t want to reboot and want to start using the package immediately, run:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;installpkg &amp;#x3C;package&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For example, to install &lt;code&gt;screen&lt;/code&gt;, I ran:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;installpkg screen-4.9.0-x86_64-1.txz&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You need to be in the directory where the package was downloaded!&lt;/p&gt;
&lt;h1 id=&quot;sources&quot;&gt;Sources&lt;/h1&gt;
&lt;p&gt;Thanks to all of the users and developers on the UnRAID forums! I referenced the following posts and comments while writing this blog:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://forums.unraid.net/topic/35866-unraid-6-nerdpack-cli-tools-iftop-iotop-screen-kbd-etc&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://forums.unraid.net/topic/35866-unraid-6-nerdpack-cli-tools-iftop-iotop-screen-kbd-etc&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://forums.unraid.net/topic/35866-unraid-6-nerdpack-cli-tools-iftop-iotop-screen-kbd-etc/?do=findComment&amp;#x26;comment=1159381&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://forums.unraid.net/topic/35866-unraid-6-nerdpack-cli-tools-iftop-iotop-screen-kbd-etc/?do=findComment&amp;#x26;comment=1159381&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://forums.unraid.net/topic/35866-unraid-6-nerdpack-cli-tools-iftop-iotop-screen-kbd-etc/?do=findComment&amp;#x26;comment=1172107&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://forums.unraid.net/topic/35866-unraid-6-nerdpack-cli-tools-iftop-iotop-screen-kbd-etc/?do=findComment&amp;#x26;comment=1172107&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Frequent disconnections on GaN chargers</title><link>https://ericswpark.com/blog/2022/2022-09-23-frequent-disconnections-on-gan-chargers/</link><guid isPermaLink="true">https://ericswpark.com/blog/2022/2022-09-23-frequent-disconnections-on-gan-chargers/</guid><pubDate>Fri, 23 Sep 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I was working on my laptop today when my phone started to emit the charging chime, over and over again, every tens of seconds or so. There was no rhyme or reason to it. The C-to-Lightning cable was in good shape, the charger itself was firmly plugged into the wall, and yet the issue persisted even after I reseated all the connections a couple of times and checked the charging port to make sure there was no debris preventing a good connection.&lt;/p&gt;
&lt;p&gt;The only likely culprit I could think of was the charger itself. I’d picked up &lt;a href=&quot;https://www.11st.co.kr/products/4171195454?&amp;#x26;xfrom=&amp;#x26;xzone=&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;this nice gallium nitride (GaN) charger&lt;/a&gt; in April this year, that could output 200 watts and could charge four of my devices at once. (Note: this is not a sponsored post or anything. I just linked what I bought because the product description page has the warning which I will show further down in this post.)&lt;/p&gt;
&lt;p&gt;I initially assumed I’d gotten a dud, and was looking through my purchase history to see if I was within the exchange window, when something in the product description caught my eye.&lt;/p&gt;
&lt;p&gt;(Translated from the Korean description:)&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Warnings during use:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;(...)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;3. When using two or more ports, or when the charging output values fluctuate significantly, charging may temporarily stop and then resume to route power between devices.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;(...)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s when it finally clicked: I had my phone and laptop connected to the same GaN charger. Opening up the power meter on my laptop, I could clearly see that the power was fluctuating based on the usage:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;power-meter&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1028&quot; height=&quot;850&quot; src=&quot;/_astro/power-meter.D4TpIKQL_CcaN.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;The GaN charger was stopping charging for a while based on the fluctuating needs of the laptop, which was causing my iPhone to stop charging and start charging again every couple of seconds. To test if this was truly the culprit, I unplugged my laptop and continued working. The problem didn’t come back.&lt;/p&gt;
&lt;p&gt;The temporary solution?&lt;/p&gt;
&lt;p&gt;Flip the mute switch, face down on desk. No more interruptions.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Note: this might not be a GaN charger specific issue. There may be chargers made with conventional tech that does the same thing that I may not know about. But I only first saw this disclaimer when purchasing GaN chargers, hence this blog post.&lt;/p&gt;</content:encoded></item><item><title>Keep SMART tests from failing from idle timeout</title><link>https://ericswpark.com/blog/2022/2022-09-17-keep-smart-tests-from-failing-from-idle-timeout/</link><guid isPermaLink="true">https://ericswpark.com/blog/2022/2022-09-17-keep-smart-tests-from-failing-from-idle-timeout/</guid><pubDate>Sat, 17 Sep 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you have a hard drive with a large capacity, extended SMART tests will take a long time to complete. If the operating system parks the head of the hard drive because of inactivity, then the SMART test will get interrupted.&lt;/p&gt;
&lt;p&gt;If the SMART test is interrupted, &lt;code&gt;smartctl&lt;/code&gt; will often report that the test is still running, preventing you from running another test. However, &lt;code&gt;smartctl -a&lt;/code&gt; will not show that the test is running.&lt;/p&gt;
&lt;p&gt;To get out of this borked state, you can issue a &lt;code&gt;smartctl -X&lt;/code&gt;, followed by another &lt;code&gt;smartctl -t long&lt;/code&gt; (with the drive mapping, of course) to run an extended SMART test.&lt;/p&gt;
&lt;p&gt;And this time, to prevent the SMART test from being interrupted by the idle timeout, run a separate terminal instance (through something like &lt;code&gt;screen&lt;/code&gt; or &lt;code&gt;tmux&lt;/code&gt;) and issue the following command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;watch -n 10 smartctl -a /dev/sda&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Remember to replace &lt;code&gt;/dev/sda&lt;/code&gt; with your drive mapping!&lt;/p&gt;
&lt;p&gt;This should poll the hard drive every ten seconds and keep it from going into an idle state.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Thanks to &lt;a href=&quot;https://old.reddit.com/user/SI100km/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;u/SI100km&lt;/a&gt;’s &lt;a href=&quot;https://www.reddit.com/r/DataHoarder/comments/jdgeat/comment/g9hw3mk/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;comment&lt;/a&gt; for the &lt;code&gt;watch&lt;/code&gt; command.&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title>Jonsbo N1: brief text review</title><link>https://ericswpark.com/blog/2022/2022-07-04-jonsbo-n1-brief-text-review/</link><guid isPermaLink="true">https://ericswpark.com/blog/2022/2022-07-04-jonsbo-n1-brief-text-review/</guid><pubDate>Mon, 04 Jul 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This is my brief review of the Jonsbo N1 case after using it for our home NAS build.&lt;/p&gt;
&lt;h1 id=&quot;the-good&quot;&gt;The Good&lt;/h1&gt;
&lt;p&gt;It’s sleek and fits well on my desk.&lt;/p&gt;
&lt;h1 id=&quot;the-bad&quot;&gt;The Bad&lt;/h1&gt;
&lt;p&gt;The fan on the case is weak, and drives usually go above &lt;del&gt;48C&lt;/del&gt; 54C (!) when under load. Not the end of the world, but I generally aim to keep my drives around the 30-40C mark, and the fan is NOT adequate for that.&lt;/p&gt;
&lt;p&gt;Good news is, the fan is replaceable (I think), but the problem is I’m not sure if it will improve the airflow situation since the case is already constrained. I’ll replace the fan when it fails and update this post if it helps any.&lt;/p&gt;
&lt;h1 id=&quot;the-ugly&quot;&gt;The Ugly&lt;/h1&gt;
&lt;p&gt;The structure and design of the case may interfere with your motherboard SATA ports (it did for me). So I only had two out of my four SATA ports usable.&lt;/p&gt;
&lt;p&gt;If it doesn’t, you still have to source one more SATA port, since most M-ITX motherboards only come with four SATA ports and the N1 has five drive bays. If your motherboard has a M.2 slot, you can stick a M.2 to SATA card in there (being mindful of the M.2 accepted “keys” – my motherboard only supports the non-SATA, NVMe “key”). There are plenty of SATA controller M.2 sticks on AliExpress that you can use, although I cannot guarantee data integrity with those sticks.&lt;/p&gt;
&lt;p&gt;Alternatively, you can get a LSI RAID card and flash it into IT mode, but that takes up one of the PCIe ports.&lt;/p&gt;
&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Next time, I’ll just build my NAS in a larger case, even if it’s more difficult to lug around and transport. M-ITX cases don’t make sense for building NASes. The Jonsbo N1 is certainly a nice M-ITX NAS case for casual home users, but if you start to expand past several terabytes it might be worthwhile to consider your options.&lt;/p&gt;
&lt;h1 id=&quot;update&quot;&gt;Update&lt;/h1&gt;
&lt;h2 id=&quot;july-14th-2022&quot;&gt;July 14th, 2022&lt;/h2&gt;
&lt;p&gt;I sent an email to the Jonsbo customer support team and they sent back this response:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Dear Eric,&lt;/p&gt;
&lt;p&gt;In summer time, the HDD temperature is usually a little bit higher, but usually if it is under 60℃ it should be okay.&lt;/p&gt;
&lt;p&gt;In any event, if you really want to change to use a more powerful fan, the standard is 14025, which mean 140 x 140 x 25 mm.
Noctua may be a good choice.&lt;/p&gt;
&lt;p&gt;I hope my information is useful for you.&lt;/p&gt;
&lt;p&gt;Have a good day.&lt;/p&gt;
&lt;p&gt;Best regards,
[name redacted]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Once one of the hard drives die from the heat, I’ll replace the fan then along with the dead hard drive. It’s good to know they used standard dimensions! Good job, Jonsbo.&lt;/p&gt;</content:encoded></item><item><title>GiGA Genie turns off my TV problem</title><link>https://ericswpark.com/blog/2022/2022-06-25-giga-genie-turns-off-my-tv-problem/</link><guid isPermaLink="true">https://ericswpark.com/blog/2022/2022-06-25-giga-genie-turns-off-my-tv-problem/</guid><pubDate>Sat, 25 Jun 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We had a weird malfunction today with our home TV, and I’m not sure why the hell the KT (Korea Telecom) engineers designed and deployed this buggy implementation.&lt;/p&gt;
&lt;p&gt;(Context: the GiGA Genie is an IPTV set-top box line-up from KT, Korea Telecom, sold here in South Korea for users that have a service contract with KT for Internet and TV. This article talks about the GiGA Genie 3 device. Other legacy devices may have unrelated problems.)&lt;/p&gt;
&lt;h1 id=&quot;the-problem&quot;&gt;The Problem&lt;/h1&gt;
&lt;p&gt;When I press the “combined power” button on the GiGA Genie remote to turn on the TV, the TV turns on, then immediately turns off. When that happens, the HDMI-CEC sync kicks in, and the GiGA Genie unit shuts off as well, which means we’re now stuck in a loop where the TV and the GiGA Genie unit never turns on.&lt;/p&gt;
&lt;p&gt;(In rare circumstances you can spam the power button to turn on the units before they have a chance to sync their status over HDMI-CEC, but this only worked once every five times during testing.)&lt;/p&gt;
&lt;p&gt;During this loop, when the TV and the GiGA Genie unit turns on, you can see two dots on the GiGA Genie unit (resembling a little face, almost), where the LED clock usually is. And then once the GiGA Genie boots, the TV shuts off.&lt;/p&gt;
&lt;h1 id=&quot;the-solution&quot;&gt;The Solution&lt;/h1&gt;
&lt;p&gt;Really simple. Inside “Genie Settings”, find the menu named “Use IR to control TV” (IR로 TV 제어) or “Use voice control to control IR household appliances” (음성으로 IR 가전 제어), and turn off the “TV control” (TV 제어) option.&lt;/p&gt;
&lt;h1 id=&quot;cause&quot;&gt;Cause&lt;/h1&gt;
&lt;p&gt;There are three reasons why a TV connected to a GiGA Genie turns on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The GiGA Genie remote’s “combined power” button, or the “TV power” button has been pressed&lt;/li&gt;
&lt;li&gt;While the GiGA Genie unit and TV are connected over HDMI-CEC, the GiGA Genie unit has been switched on&lt;/li&gt;
&lt;li&gt;The GiGA Genie unit receives a request (“Hey Genie, turn on the TV”) to power on the TV using the built-in IR blaster&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Before, the last option – IR blaster – only emitted IR signals when you used voice commands, but a recent firmware update seems to have broken the functionality. Now, when the GiGA Genie unit turns on, it detects that the TV is off and tries to turn it on using IR, and that’s where the bug lies.&lt;/p&gt;
&lt;p&gt;TL;DR: Turn on the TV and GiGA Genie unit using the “combined power” button -&gt; TV turns on -&gt; GiGA Genie unit turns on and turns on the TV using the IR blaster -&gt; The TV, having received the signal from the GiGA Genie unit, switches off -&gt; Through HDMI-CEC, the GiGA Genie notices the TV has shut off and powers off as well -&gt; Infinite loop&lt;/p&gt;
&lt;p&gt;Please fix this, KT :(&lt;/p&gt;</content:encoded></item><item><title>Setting write cache on drives on UnRAID</title><link>https://ericswpark.com/blog/2022/2022-06-25-setting-write-cache-on-drives-on-unraid/</link><guid isPermaLink="true">https://ericswpark.com/blog/2022/2022-06-25-setting-write-cache-on-drives-on-unraid/</guid><pubDate>Sat, 25 Jun 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;You might have run into this issue where the “Fix Common Problems” plugin gave you this warning:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Write Cache is disabled on parity/disk1/diskX&lt;/p&gt;
&lt;p&gt;You may experience slow read/writes to parity/disk1/diskX. Write Cache should be enabled for better results. Read this post ( &lt;a href=&quot;https://forums.unraid.net/topic/46802-faq-for-unraid-v6/page/2/?tab=comments#comment-755621&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://forums.unraid.net/topic/46802-faq-for-unraid-v6/page/2/?tab=comments#comment-755621&lt;/a&gt; for more information. (…)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you follow the link, you will see a recommendation to run &lt;code&gt;hdparm -W 1 /dev/sdX&lt;/code&gt; and see whether it is successful, then add these commands to a script under the “User Scripts” plugin and set the scheduler so that it runs when the array is first mounted.&lt;/p&gt;
&lt;p&gt;So you may write a script like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#!/bin/bash&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;hdparm&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -W&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /dev/sdb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;hdparm&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -W&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /dev/sdc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;hdparm&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -W&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /dev/sdd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# ... and so on ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This script can be improved in two ways.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Since we are running the same command with different parameters, group them into an array and iterate over it, like so:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#!/bin/bash&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;declare&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -a&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; drives&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;sdb&quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;sdc&quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;sdd&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# add more here&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; drive &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;drives&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;]}&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    hdparm&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -W&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /dev/&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$drive&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;done&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Do not use &lt;code&gt;/dev/sdX&lt;/code&gt; to access disks!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Why? Because these are not permanent, and may change across reboots as Linux sees fit!&lt;/p&gt;
&lt;p&gt;A better solution would be to utilize the IDs that the drives come with. Run the following on your UnRAID server:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ls&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -al&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /dev/disk/by-id&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; grep&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;ata&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; grep&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -v&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;\-part&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will list the drives on your system, with their IDs and what &lt;code&gt;/dev/sdX&lt;/code&gt; they map into:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Example IDs, obviously&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;lrwxrwxrwx&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; root&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; root&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;   9&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Jun&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 25&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 21:33&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ata-WDC_WD1234ABCD-567890_WJAN01234567&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ../../sdb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;lrwxrwxrwx&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; root&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; root&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;   9&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Jun&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 25&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 21:33&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ata-WDC_WD1234ABCD-567890_WJAN01234568&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ../../sdc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;lrwxrwxrwx&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; root&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; root&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;   9&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Jun&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 25&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 21:33&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ata-WDC_WD1234ABCD-567890_WJAN01234569&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ../../sdd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# ... and so on ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once you re-write the script, you should end up with something like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#!/bin/bash&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;declare&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -a&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; drives&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;WDC_WD1234ABCD-567890_WJAN01234567&quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;WDC_WD1234ABCD-567890_WJAN01234568&quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;WDC_WD1234ABCD-567890_WJAN01234569&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# add more here&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; drive &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;drives&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;]}&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    hdparm&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -W&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /dev/disk/by-id/ata-&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$drive&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;done&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Stick it in your “User Scripts” and set the schedule, and you should be good to go!&lt;/p&gt;</content:encoded></item><item><title>UnRAID encryption auto-start</title><link>https://ericswpark.com/blog/2022/2022-06-22-unraid-encryption-auto-start/</link><guid isPermaLink="true">https://ericswpark.com/blog/2022/2022-06-22-unraid-encryption-auto-start/</guid><pubDate>Wed, 22 Jun 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;You want to start your encrypted UnRAID array automatically after a reboot. Normally you’d follow the procedure &lt;a href=&quot;https://forums.unraid.net/topic/61973-encryption-and-auto-start/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;outlined in this thread&lt;/a&gt;, but what if you didn’t want to run another SMB share? What if you didn’t want a server that constantly serves the password file, unprotected?&lt;/p&gt;
&lt;p&gt;Well, here’s another solution you can opt for.&lt;/p&gt;
&lt;h1 id=&quot;procedure&quot;&gt;Procedure&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;On another computer on the same network as the UnRAID server, create and run the following Python script:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;main.py&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#!/usr/bin/env python3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; os.path &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; exists&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; http.server&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Define settings here&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;PORT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 9999&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;PASSWORD_FILE_NAME&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;password.txt&quot;&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; # in current directory&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;TIMEOUT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 300&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   # in seconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Read or get password&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; exists(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;PASSWORD_FILE_NAME&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;f&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Password file &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{PASSWORD_FILE_NAME}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; exists, reading password from it&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    with&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; open&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;PASSWORD_FILE_NAME&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; password_file:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        PASSWORD_TO_SEND&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; password_file.readline().strip()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; getpass &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; getpass&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Password file does not exist. Please enter one now.&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    PASSWORD_TO_SEND&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; getpass()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Set up webserver&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; RequestHandler&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;server&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;BaseHTTPRequestHandler&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; do_GET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(s):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        s.send_response(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;200&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        s.send_header(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Content-Type&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;text/plain&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        s.end_headers()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        s.wfile.write(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;str&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.encode(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;PASSWORD_TO_SEND&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;httpd &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; http.server.HTTPServer((&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;PORT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;), RequestHandler)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;httpd.timeout &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; TIMEOUT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;httpd.handle_request()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Some notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a &lt;code&gt;password.txt&lt;/code&gt; file in the same directory as the script and save the encryption password there.&lt;/li&gt;
&lt;li&gt;Set the timeout longer than the time it takes your UnRAID server to reboot.&lt;/li&gt;
&lt;li&gt;If you want to manually enter the password and not have it saved as a text file on your other computer, just delete the &lt;code&gt;password.txt&lt;/code&gt; and the script will prompt you to enter a password when it runs.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Modify the UnRAID flash so that it fetches the key from your other computer.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;(Special thanks to &lt;a href=&quot;https://forums.unraid.net/profile/2736-bonienl/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;@bonienl&lt;/a&gt; on the UnRAID forums for the &lt;a href=&quot;https://forums.unraid.net/topic/61973-encryption-and-auto-start/?do=findComment&amp;#x26;comment=648148&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;original script&lt;/a&gt;. I’ve tweaked it slightly for it to work with this alternate solution.)&lt;/p&gt;
&lt;p&gt;If you have the USB stick mounted on another computer, you can omit the &lt;code&gt;/boot/&lt;/code&gt; part from the file path.&lt;/p&gt;
&lt;p&gt;Edit &lt;code&gt;/boot/config/go&lt;/code&gt; and add/modify the following lines:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# ...snip...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Copy scripts to emhttp event directories&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;install&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -D&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /boot/custom/keyscript/fetch_key&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /usr/local/emhttp/webGui/event/starting/fetch_key&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;install&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -D&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /boot/custom/keyscript/delete_key&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /usr/local/emhttp/webGui/event/started/delete_key&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Set execute permission&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;chmod&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; a+x&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /usr/local/emhttpd/webGui/event/starting/fetch_key&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;chmod&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; a+x&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /usr/local/emhttpd/webGui/event/started/delete_key&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Start webGUI&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;/usr/local/sbin/emhttp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &amp;#x26;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create &lt;code&gt;/boot/custom/keyscript/fetch_key&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#!/bin/bash&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [[ &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; -e&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; /root/keyfile ]]; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  curl&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; other-computer:9999&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /root/keyfile&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; # Make sure to substitute port here if you changed it!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;fi&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create &lt;code&gt;/boot/custom/keyscript/delete_key&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#!/bin/bash&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;rm&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -f&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /root/keyfile&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Now, when you need to reboot your UnRAID machine, remote into your other computer, execute the Python script, and reboot. Upon reboot, the UnRAID server will get the encryption password from your other computer and unlock and mount the array automatically.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here’s a script that does the above automatically for you:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#!/bin/bash&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Start encryption password server script&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;python3&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; main.py&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &amp;#x26;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# SSH into server and issue reboot command&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Make sure to change hostname to correct value&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ssh&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; root@unraid-server-hostname&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; reboot&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;troubleshooting&quot;&gt;Troubleshooting&lt;/h1&gt;
&lt;h2 id=&quot;bonjour-issues&quot;&gt;Bonjour issues&lt;/h2&gt;
&lt;p&gt;If you get an error about either the UnRAID server or the other computer not being able to find the host, your network may not support hostname-based discovery. To mitigate this, give your machines (both your UnRAID server and your other computer) a static IP, and use static IPs in the scripts instead.&lt;/p&gt;
&lt;h1 id=&quot;benefits&quot;&gt;Benefits&lt;/h1&gt;
&lt;p&gt;Here are some of the benefits with this approach compared to other solutions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The password is only exposed to the local network for a brief period, instead of a server constantly running that exposes the password. Once the timeout is reached, the script that emulates a simple HTTP server terminates.&lt;/li&gt;
&lt;li&gt;If someone gains unauthorized access to your UnRAID server and can inspect the contents of the flash drive, they will be able to tell that the server fetches the password from the other computer in order to start up, but they will not be able to know what the password is (unless, of course, the server is already running, then in which case they can simply inspect the memory).&lt;/li&gt;
&lt;li&gt;If someone gains unauthorized access to your other computer, they will be able to tell that there is a script that serves the password to the UnRAID server, but they will not be able to know what the password is, unless you’ve opted to save the password as a text file. In that case, you can simply lock the computer remotely and they will not be able to access the password.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;downsides&quot;&gt;Downsides&lt;/h1&gt;
&lt;p&gt;You may want to review a couple of flaws with this approach:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The communication between your other computer on the network and the UnRAID server is unencrypted. Normally this shouldn’t be an issue on your home network with trusted devices only, but if there is an eavesdropper on your network then they can intercept your encryption password. Consider reviewing your network security before implementing this.&lt;/li&gt;
&lt;li&gt;This requires another computer to be on and accessible on the same network as the UnRAID machine. If your UnRAID server has, for example, your DHCP server, then this approach will not work as the UnRAID server won’t be able to reach your other computer until it has the array started. To mitigate this, ensure that your UnRAID server is not responsible for any part of your network stack.&lt;/li&gt;
&lt;li&gt;The password file sits on your other computer unprotected. You can mitigate the potential risk by typing in the password every time instead of keeping it saved.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;As I was writing down this blog post I realized that if you already have access to another computer on the same network as the UnRAID server, you could simply access the WebUI and type in the password after the UnRAID server starts up. So this approach is stupid, right?&lt;/p&gt;
&lt;p&gt;Well, this approach will also work for computers where you don’t have graphical access to (such as a headless machine without VNC/RDP access). You will still be able to reboot the server by running this script before rebooting.&lt;/p&gt;
&lt;p&gt;As this approach is a bit risky in terms of security, you should carefully evaluate your network security and your threat model before you deploy it.&lt;/p&gt;</content:encoded></item><item><title>[Update: Blocked] Use a different launcher on Vivo phones</title><link>https://ericswpark.com/blog/2022/2022-06-20-use-a-different-launcher-on-vivo-phones/</link><guid isPermaLink="true">https://ericswpark.com/blog/2022/2022-06-20-use-a-different-launcher-on-vivo-phones/</guid><pubDate>Mon, 20 Jun 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h1 id=&quot;the-problem&quot;&gt;The problem&lt;/h1&gt;
&lt;p&gt;Vivo phones come with this weird restriction where you cannot change the default launcher… without signing into a Vivo account.&lt;/p&gt;
&lt;p&gt;You can try and change the default launcher. But even though the menu looks like it will let you, as soon as you hit the home button, the default launcher will be reset to Vivo’s own.&lt;/p&gt;
&lt;p&gt;…but I don’t want to create a Vivo account and have no intentions of giving over any data to Vivo, and even if I could make a burner account I find the entire process to be too much hassle. Instead, we’re going to force the phone to accept another default launcher, dammit!&lt;/p&gt;
&lt;p&gt;(Note: this was tested on the Vivo Y93, the only Vivo phone I have right now. Fortunate because it’s the only crappy Vivo phone I’ll ever own, unfortunate because this was not my decision and because it’s a leftover phone obtained from a family member. If I don’t find an alternative use for this phone, it’s basically e-waste. This may not work on all Vivo phones and if the process fails the device will be soft-bricked! Be careful and proceed at your own risk!)&lt;/p&gt;
&lt;h1 id=&quot;the-solution&quot;&gt;The solution&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Install a different launcher, like Nova Launcher. I downloaded the APK from &lt;a href=&quot;apkmirror.com&quot;&gt;apkmirror.com&lt;/a&gt; and installed it with ADB, since Chinese phones do not have the Google Play Store. (I’m pretty sure I can somehow wrangle it on there, but I don’t think I want to, since that would involve logging in with my Google account, and I don’t want to on this device.)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; Enable developer options and run:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;adb shell pm disable-user --user 0 com.bbk.launcher2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(Note: the package name &lt;code&gt;com.bbk.launcher2&lt;/code&gt; may be different if you are using a different Vivo phone. Also, the command may fail if they patched out this method in newer phones. In that case, you may be out of luck :( )&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; The Vivo default home launcher will now be removed, and hitting the home button should bring up a menu showing you which launcher you’d like to set as default. Select the different launcher you installed in step 1 and make sure the “Make default” checkbox is checked.&lt;/p&gt;
&lt;p&gt;Done! Screw you, Vivo.&lt;/p&gt;
&lt;h1 id=&quot;update&quot;&gt;Update&lt;/h1&gt;
&lt;p&gt;As of October 21st, 2022, this method is now blocked. Disabling the built-in launcher will result in an infinite loop of selecting the default launcher, which will make the phone unusable. The only way back is to either restore the built-in launcher through &lt;code&gt;adb&lt;/code&gt; or through a factory reset.&lt;/p&gt;</content:encoded></item><item><title>Properly install Docker on macOS</title><link>https://ericswpark.com/blog/2022/2022-06-06-properly-install-docker-on-macos/</link><guid isPermaLink="true">https://ericswpark.com/blog/2022/2022-06-06-properly-install-docker-on-macos/</guid><pubDate>Mon, 06 Jun 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;TL;DR: You want to run&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;brew install --cask docker&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to install Docker Desktop, &lt;em&gt;not&lt;/em&gt; anything else. Read on for more information.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;First I tried:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;brew install docker docker-compose&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and the command finished successfully. So I ran &lt;code&gt;docker --version&lt;/code&gt; and &lt;code&gt;docker-compose --version&lt;/code&gt; and both commands correctly displayed their respective version strings.&lt;/p&gt;
&lt;p&gt;So I went inside a directory with a &lt;code&gt;docker-compose.yml&lt;/code&gt; file and tried to start up the services with &lt;code&gt;docker-compose up -d&lt;/code&gt;, but then Docker complained that it couldn’t find a daemon running. So I wasted twenty minutes checking if Homebrew had a service I could enable, or if there was some other way of starting the Docker daemon.&lt;/p&gt;
&lt;p&gt;Turns out, the command &lt;code&gt;brew install docker&lt;/code&gt; only installs the Docker &lt;em&gt;client&lt;/em&gt;, not the server service required to actually run the Docker Engine and all the stuff underneath. That’s not available on macOS, because it’s not Linux, and doesn’t support all the containerization technology that Linux has, like &lt;code&gt;cgroup&lt;/code&gt;s and what not. The workaround that the Docker team came up with is to run a minimal version of Linux on a VM that the Docker client can communicate with.&lt;/p&gt;
&lt;p&gt;So we need to run the following:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;brew install docker-machine&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;brew install --cask virtualbox&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to install the Docker Machine scripts and VirtualBox, which the Docker Machine scripts rely on. This requires a system reboot, because VirtualBox still uses kernel extensions. Um, okay, then. Time to save all my work and reboot.&lt;/p&gt;
&lt;p&gt;Now we need to create a machine the Docker daemon can run on. So I ran:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;docker-machine create --driver virtualbox default&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But the command errored out after a while with a &lt;code&gt;VBoxManage: error: Code E_ACCESSDENIED&lt;/code&gt;. So I dug deeper. It turns out that Docker Machine was deprecated sometime in September 2019, so we shouldn’t use it.&lt;/p&gt;
&lt;p&gt;Okay, what then? Well, we use Docker Desktop, which is available as a Homebrew Cask and has (apparently) support for the new Apple Silicon-based Macs.&lt;/p&gt;
&lt;p&gt;So we remove all the nonsense I did for the past couple of hours:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;docker-machine rm default   # press `y` to accept&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;rm -rf .docker              # Be careful! Make sure there&apos;s nothing in .docker/ you wish to keep&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;brew uninstall docker-machine docker docker-compose virtualbox&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we now install the correct Docker Desktop Cask:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;brew install --cask docker&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;So we got Docker working, right? Well, yes. But I really wanted to use just the Docker engine and not Docker Desktop, because Docker Desktop is proprietary and subject to license agreements with the corporation behind Docker. Only the Docker Engine is open-source and free to use, but it’s not available on macOS.&lt;/p&gt;
&lt;p&gt;In the end, you’ll have to either use Docker Desktop on your macOS development machines, or set up another Linux instance that you can run Docker containers on.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Update: since writing this post I’ve come across the &lt;a href=&quot;https://github.com/abiosoft/colima&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;colima project&lt;/a&gt;, which aims to provide container runtimes on macOS through QEMU. It’s not perfect and still being actively developed &lt;del&gt;and doesn’t have support for Apple Silicon Macs yet&lt;/del&gt;, which is why I won’t detail them here in this post. But it is definitely a project you should keep an eye on. I might switch to this once it matures.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Update 2 (2025-11-01): A reader reached out to let me know that Colima now has support for Apple Silicon Macs! Thanks to Wolf Eggers for the heads-up.&lt;/p&gt;</content:encoded></item><item><title>Netflix hates my HDMI dongle</title><link>https://ericswpark.com/blog/2022/2022-05-19-netflix-hates-my-hdmi-dongle/</link><guid isPermaLink="true">https://ericswpark.com/blog/2022/2022-05-19-netflix-hates-my-hdmi-dongle/</guid><pubDate>Thu, 19 May 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Just a warning to anybody trying to mirror Netflix using &lt;a href=&quot;https://www.apple.com/shop/product/MD826AM/A/lightning-digital-av-adapter&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Apple’s Digital AV to Lightning adapter&lt;/a&gt;: it won’t work.&lt;/p&gt;
&lt;p&gt;(There used to be a tweet embedded here but it’s gone as I deleted my account.)&lt;/p&gt;
&lt;p&gt;Thanks, Netflix DRM!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The Netflix CS team sent me a couple of replies since the initial tweet. In case you’re wondering what the Netflix team did, mostly nothing at this point. They contacted me via Twitter DM and told me they are “investigating,” and that they had forwarded my “report” to the “Research Team.” I haven’t heard back from them as of writing this (May 20th, 2022). I will update this post accordingly if I hear back from them.&lt;/p&gt;</content:encoded></item><item><title>Terminal incognito mode</title><link>https://ericswpark.com/blog/2021/2021-11-13-terminal-incognito-mode/</link><guid isPermaLink="true">https://ericswpark.com/blog/2021/2021-11-13-terminal-incognito-mode/</guid><pubDate>Sat, 13 Nov 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Sometimes you have to run a bunch of commands that you would rather not get saved to your terminal history, such as commands that manage GPG keys, for example. In which case, the following snippet might be useful for you.&lt;/p&gt;
&lt;p&gt;Add the following to &lt;code&gt;.zshrc&lt;/code&gt; if you use &lt;code&gt;zsh&lt;/code&gt;, or &lt;code&gt;.bashrc&lt;/code&gt; (or even &lt;code&gt;.bash_profile&lt;/code&gt;) if you use &lt;code&gt;bash&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;function incognito() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    unset HISTFILE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    export PROMPT=&apos;[INCOGNITO] %n@%m %1~ %# &apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You may need to edit &lt;code&gt;PROMPT&lt;/code&gt; to say &lt;code&gt;PS1&lt;/code&gt; depending on the terminal you are using. I’ve borrowed the &lt;code&gt;PS1&lt;/code&gt; prompt from the default set on macOS. Edit to your liking and OS defaults if desired.&lt;/p&gt;
&lt;p&gt;Now, when you run &lt;code&gt;incognito&lt;/code&gt;, the current terminal session will not get saved to the history file. Happy coding!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;If you would like an explanation as to how this works, it’s quite simple. Terminals use a history file, such as &lt;code&gt;.bash_history&lt;/code&gt; or &lt;code&gt;.zsh_history&lt;/code&gt; to record your previous commands so that you can bring them up with the &lt;code&gt;history&lt;/code&gt; command or the Ctrl-R keystroke (reverse search through your command history). This is set in the &lt;code&gt;HISTFILE&lt;/code&gt; variable, and unsetting it prevents the terminal from finding the history file, which means your current session is not saved.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;update&quot;&gt;Update&lt;/h2&gt;
&lt;p&gt;If you have &lt;a href=&quot;https://github.com/romkatv/powerlevel10k/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Powerlevel10k&lt;/a&gt; (a ZSH theme) installed like I do, the prompt customization with &lt;code&gt;export PROMPT&lt;/code&gt; won’t work. Thankfully, the theme is awesome, and it lets you define custom segments to put on your prompt bar.&lt;/p&gt;
&lt;p&gt;Edit &lt;code&gt;~/.p10k.zsh&lt;/code&gt; and add the following to the end:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Custom incognito segment&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;function prompt_is_incognito_mode() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if (( ! ${+HISTFILE} )); then&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        p10k segment -f red -i &apos;🥷&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    fi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then find the part that defines &lt;code&gt;POWERLEVEL9K_LEFT_PROMPT_ELEMENTS&lt;/code&gt; and add &lt;code&gt;is_incognito_mode&lt;/code&gt; to the front (or anywhere you prefer):&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  # The list of segments shown on the left. Fill it with the most important segments.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  typeset -g POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    # Incognito mode indicator&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    is_incognito_mode&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    # =========================[ Line #1 ]=========================&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    # os_icon               # os identifier&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    dir                     # current directory&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    vcs                     # git status&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    # =========================[ Line #2 ]=========================&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    newline                 # \n&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    prompt_char             # prompt symbol&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  )&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also, you can change the &lt;code&gt;incognito&lt;/code&gt; function in &lt;code&gt;~/.zshrc&lt;/code&gt; like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Function for &quot;incognito&quot; mode&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;function incognito() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    unset HISTFILE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    echo &quot;Done! This terminal session will not be recorded into history.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, when you run &lt;code&gt;incognito&lt;/code&gt;, you’ll be greeted by this cute little ninja:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&gt; incognito&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Done! This terminal session will not be recorded into history.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; 🥷  | ~&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title>FUSE mounting quirks on Linux (with VeraCrypt)</title><link>https://ericswpark.com/blog/2021/2021-11-10-fuse-mounting-quirks-on-linux-with-veracrypt/</link><guid isPermaLink="true">https://ericswpark.com/blog/2021/2021-11-10-fuse-mounting-quirks-on-linux-with-veracrypt/</guid><pubDate>Wed, 10 Nov 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you get a&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Permission denied: file.hc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;VeraCrypt::File::Open:232&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;error when attempting to mount a VeraCrypt volume on Linux, this is the post for you!&lt;/p&gt;
&lt;h1 id=&quot;why-does-this-happen&quot;&gt;Why does this happen?&lt;/h1&gt;
&lt;p&gt;This usually occurs because the encrypted VeraCrypt file resides on a FUSE mount that VeraCrypt cannot access.&lt;/p&gt;
&lt;p&gt;To explain further, FUSE is used whenever you need to mount a custom filesystem, say a filesystem based on SSH (SSHFS), or a Samba share (such as from your NAS), or from another encryption software (such as a mount from VeraCrypt itself or Cryptomator). FUSE assigns a mount point, does the complex translation between your computer and the save medium, and you can happily write away at the mount without knowing of all the details.&lt;/p&gt;
&lt;p&gt;The issue occurs because on Linux, FUSE restricts who can access a given mount based on who mounted the volume in the first place. This applies to the superuser as well, on defaults, anyway. If this sounds complicated, just know that whoever first mounted the filesystem is the only one who can access the mount, and nobody else can.&lt;/p&gt;
&lt;p&gt;VeraCrypt requires superuser permissions, since it tries to mount the volume at &lt;code&gt;/mnt&lt;/code&gt;, or some other system mount directory that requires superuser permissions. Therefore, it runs as the &lt;code&gt;root&lt;/code&gt; user. Remember the restriction from above? Even as the &lt;code&gt;root&lt;/code&gt; user, FUSE disallows access to the previous mount that holds the encrypted VeraCrypt file, which means VeraCrypt can’t access the file to open it.&lt;/p&gt;
&lt;h1 id=&quot;how-can-you-solve-it&quot;&gt;How can you solve it?&lt;/h1&gt;
&lt;p&gt;One of several ways:&lt;/p&gt;
&lt;h2 id=&quot;mount-as-root&quot;&gt;Mount as &lt;code&gt;root&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;You can simply mount the underlying filesystem as the &lt;code&gt;root&lt;/code&gt; user, which will allow VeraCrypt to mount the file without any problems. Unfortunately, this has the side effect of not allowing you, the user, to actually modify any files on the underlying mount, which is cumbersome if the mount is, say, a Samba share from a NAS and you want to actually change any other file on your NAS.&lt;/p&gt;
&lt;h2 id=&quot;configure-fuse-to-allow-other-users-or-root&quot;&gt;Configure FUSE to allow other users (or &lt;code&gt;root&lt;/code&gt;)&lt;/h2&gt;
&lt;p&gt;Add the following to your &lt;code&gt;/etc/fuse.conf&lt;/code&gt; file:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;user_allow_other&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the line exists, make sure it is uncommented so that it is enabled.&lt;/p&gt;
&lt;p&gt;Then, when mounting the underlying filesystem, you can pass in an optional parameter that allows other users to access the mount, or the &lt;code&gt;root&lt;/code&gt; user.&lt;/p&gt;
&lt;p&gt;To allow other users (including &lt;code&gt;root&lt;/code&gt;):&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-o allow_other&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To allow only the &lt;code&gt;root&lt;/code&gt; user:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-o allow_root&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title>Charging from untrusted sources</title><link>https://ericswpark.com/blog/2021/2021-09-11-charging-from-untrusted-sources/</link><guid isPermaLink="true">https://ericswpark.com/blog/2021/2021-09-11-charging-from-untrusted-sources/</guid><pubDate>Sat, 11 Sep 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Have you ever thought about the USB ports that are available in public places? I’m talking about USB ports on the side of airport chairs, or the ones built into the table at restaurants and coffee shops.&lt;/p&gt;
&lt;p&gt;Well, turns out plugging your devices into untrusted USB ports isn’t such a great idea. Dubbed &lt;a href=&quot;https://en.wikipedia.org/wiki/Juice_jacking&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;“juice jacking”&lt;/a&gt; by the media, you never know if the plug is just a regular old power supply plug with only power supplying capabilities, or a sinister device with circuitry to connect to your device and siphon data off of it. Because of this, people started to buy things like &lt;a href=&quot;https://int3.cc/products/usbcondoms&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;USB condoms&lt;/a&gt; that have the data pins cut so that no data exfiltration can take place.&lt;/p&gt;
&lt;p&gt;I thought that was quite a nice idea and was about to purchase one of these for when I’m out and about (that is, if the coronavirus restrictions lift again), then realized that these don’t make much sense. Here are the two major reasons why I think they aren’t worth looking into:&lt;/p&gt;
&lt;h1 id=&quot;1-the-ports-can-still-be-malicious-even-without-data-pins&quot;&gt;1. The ports can still be malicious, even without data pins&lt;/h1&gt;
&lt;p&gt;One way the ports can still damage your devices is by injecting a ton of voltage through the power pins of the USB port. It’s similar to a &lt;a href=&quot;https://en.wikipedia.org/wiki/USB_Killer&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;USB killer&lt;/a&gt; in how it works.&lt;/p&gt;
&lt;p&gt;Your phone is only rated for a certain amount of voltage, such as 5V (most common) or 9V (fast charging), etc. Therefore, a good way to kill your device or at least fry the charging circuitry of it is to inject more voltage than it is capable of receiving. And because the voltage can be injected through the power pins, these USB condoms will have no effect whatsoever, since they only disconnect data pins.&lt;/p&gt;
&lt;p&gt;And sure, I think having malicious ports at your local coffee shop is a bit far-fetched, but we’re talking about charging from &lt;strong&gt;any&lt;/strong&gt; untrusted source, which includes actively hostile environments where adversaries have installed malicious chargers like the one described above. In this case, the USB condom won’t save you.&lt;/p&gt;
&lt;h1 id=&quot;2-fast-charging-is-disabled&quot;&gt;2. Fast charging is disabled&lt;/h1&gt;
&lt;p&gt;Fast charging on modern devices work by exchanging signals through the data pins. If I remember correctly, Apple devices use resistors across the data pins to signal the maximum amount of current the devices can draw, while Android handsets use twenty different protocols (Qualcomm Quick Charge, for example) just to figure out how to quick charge, something I won’t get into here because this blog post will become horrendously long.&lt;/p&gt;
&lt;p&gt;But because the USB condom breaks the data pins, fast charging cannot be negotiated through these lines and therefore your devices will charge more slowly.&lt;/p&gt;
&lt;h1 id=&quot;whats-the-solution&quot;&gt;What’s the solution?&lt;/h1&gt;
&lt;p&gt;Use your own USB charger, or use a small powerbank as a regulator.&lt;/p&gt;
&lt;p&gt;AC plugs obviously can’t exfiltrate data, and any voltage tampering will result in the death of your USB charger, not your phone, if your USB charger is made by a reputable company with all the safety features like fuses. And it’s obviously far more cheaper to replace a broken USB charger than your phone.&lt;/p&gt;
&lt;p&gt;If you do not want to purchase a USB charger, or if you are in a environment where the only option is to use a USB port, then I would use a powerbank as an intermediary. Charge your powerbank from the untrusted USB port, and then charge your phone using the powerbank. Since the powerbank only works as a regulator between the device and the power source, you don’t even need to get a bulky powerbank with high capacity.&lt;/p&gt;</content:encoded></item><item><title>Django: Switch to custom User model mid-project</title><link>https://ericswpark.com/blog/2021/2021-07-13-django-switch-to-custom-user-model-mid-project/</link><guid isPermaLink="true">https://ericswpark.com/blog/2021/2021-07-13-django-switch-to-custom-user-model-mid-project/</guid><pubDate>Tue, 13 Jul 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This is an overview of how you can switch to a custom User model in your Django codebase if you’re already using it in production and don’t want to lose any data.&lt;/p&gt;
&lt;p&gt;I had to go through this process myself because I didn’t know that using a custom User model was a must, even if you didn’t need any extra fields. In fact, &lt;a href=&quot;https://docs.djangoproject.com/en/3.2/topics/auth/customizing/#using-a-custom-user-model-when-starting-a-project&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;the Django documentation recommends it for new projects&lt;/a&gt;, even though the warning is not prominent in any of the quick start tutorials and guides. Maybe they can emphasize this a bit more?&lt;/p&gt;
&lt;p&gt;Anyway, if you’re stuck in the same situation as me, here’s how to dig yourself out of the mess.&lt;/p&gt;
&lt;h1 id=&quot;warning&quot;&gt;Warning&lt;/h1&gt;
&lt;p&gt;Always make sure you have a backup of your database so you can downgrade back to a stable state!&lt;/p&gt;
&lt;h1 id=&quot;1-locate-a-django-app-with-an-initial-migration&quot;&gt;1. Locate a Django app with an initial migration&lt;/h1&gt;
&lt;p&gt;If you already have a Django app you can place your custom User model in, congratulations! Skip to &lt;strong&gt;step 6&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;: this existing Django app MUST have an initial migration (something like &lt;code&gt;0001_initial.py&lt;/code&gt; in the &lt;code&gt;migrations/&lt;/code&gt; directory). It doesn’t matter if the Django app no longer has any models associated with it.&lt;/p&gt;
&lt;p&gt;If not, let’s continue with creating a Django app.&lt;/p&gt;
&lt;h1 id=&quot;2-create-a-new-django-app&quot;&gt;2. Create a new Django app&lt;/h1&gt;
&lt;p&gt;Create a new Django app in your codebase with the following command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;python3&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; manage.py&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; startapp&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; accounts&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can name it whatever you want, like &lt;code&gt;users&lt;/code&gt;. I already had an &lt;code&gt;accounts&lt;/code&gt; app in my project, so that’s what I’ll use for this tutorial.&lt;/p&gt;
&lt;h1 id=&quot;3-create-a-fake-model-to-create-an-initial-migration&quot;&gt;3. Create a fake model to create an initial migration&lt;/h1&gt;
&lt;p&gt;Go into your new app and open up &lt;code&gt;models.py&lt;/code&gt;. Create a new fake model:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; django.db &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; models&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; FakeModel&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;models&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Model&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    pass&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We’ll remove this model later after everything is done.&lt;/p&gt;
&lt;h1 id=&quot;4-create-an-initial-migration&quot;&gt;4. Create an initial migration&lt;/h1&gt;
&lt;p&gt;Run:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;python3&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; manage.py&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; makemigrations&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should now have an initial migration in your app. For example, my migration was in &lt;code&gt;accounts/migrations/0001_initial.py&lt;/code&gt; with the following content:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Generated by Django 3.2.5 on 2021-07-12 16:28&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; django.db &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; migrations, models&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Migration&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;migrations&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Migration&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    initial &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; True&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    dependencies &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    operations &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        migrations.CreateModel(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;            name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;FakeModel&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;            fields&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;id&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.BigAutoField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;auto_created&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;primary_key&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;serialize&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;False&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;ID&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        ),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;5-release-this-go-between-release-to-admins&quot;&gt;5. Release this go-between release to admins&lt;/h1&gt;
&lt;p&gt;Now, make a new release and make sure all server admins &lt;strong&gt;update to this version first. All instances must perform a migration on this release at least once.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The reason is because Django keeps track of what migrations it has applied. If we created a new migration that created a new table for the User model, Django would try to apply that migration, but fail because the table already exists.&lt;/p&gt;
&lt;p&gt;To solve this problem, if we migrate all previous instances using a go-between release, and then patch the migration with the User model migration information, then existing instances will not apply the migration twice, and new instances will be able to create the User model since it’s an initial release.&lt;/p&gt;
&lt;h1 id=&quot;6-create-a-custom-user-model&quot;&gt;6. Create a custom user model&lt;/h1&gt;
&lt;p&gt;If you came here straight from &lt;strong&gt;step 1&lt;/strong&gt;, then use the app that you chose that already has an initial migration. For context, my app is &lt;code&gt;accounts&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We need to now create a custom user model. Edit &lt;code&gt;models.py&lt;/code&gt; and add the following:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; django.contrib.auth.models &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; AbstractUser&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; User&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AbstractUser&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Meta&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        db_table &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;auth_user&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;db_table&lt;/code&gt; field is important because we’re trying to seamlessly transition from the existing User model table. I recommend naming the custom user model as &lt;code&gt;User&lt;/code&gt; as well to preserve relations in the database. Don’t worry, you should be able to rename it after this is done.&lt;/p&gt;
&lt;p&gt;If you followed &lt;strong&gt;steps 1-5&lt;/strong&gt;, your &lt;code&gt;models.py&lt;/code&gt; should look something like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; django.contrib.auth.models &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; AbstractUser&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; django.db &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; models&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; User&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AbstractUser&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Meta&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        db_table &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;auth_user&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; FakeModel&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;models&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Model&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    pass&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Don’t worry if you came straight from &lt;strong&gt;step 1&lt;/strong&gt; and don’t have the &lt;code&gt;FakeModel&lt;/code&gt; model.&lt;/p&gt;
&lt;h1 id=&quot;7-manually-patch-the-initial-migration&quot;&gt;7. Manually patch the initial migration&lt;/h1&gt;
&lt;p&gt;Now, edit your app’s initial migration. You should see a lot of migrations for the other models in your app.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;WARNING&lt;/strong&gt;: this patch is valid as of July 13th, 2021, and with Django version 3.2.5. Newer Django versions may have different fields and migrations. To make sure, create an empty project, make a custom user model, and create an initial migration and compare it to this section. If it has been updated, then use the values from the empty project!&lt;/p&gt;
&lt;p&gt;First, import the necessary dependencies:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; django.contrib.auth.models&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; django.contrib.auth.validators&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; django.utils.timezone&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Under &lt;code&gt;dependencies&lt;/code&gt;, add:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;auth&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;0012_alter_user_first_name_max_length&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your &lt;code&gt;dependencies&lt;/code&gt; section should now look like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    dependencies &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;auth&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;0012_alter_user_first_name_max_length&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Inside &lt;code&gt;operations&lt;/code&gt;, add:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        migrations.CreateModel(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;            name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;User&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;            fields&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;id&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.BigAutoField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;auto_created&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;primary_key&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;serialize&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;False&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;ID&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;password&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.CharField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;max_length&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;128&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;password&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;last_login&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.DateTimeField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;blank&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;last login&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;is_superuser&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.BooleanField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;False&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                     help_text&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Designates that this user has all permissions without explicitly assigning them.&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                     verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;superuser status&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;username&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.CharField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;error_messages&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;unique&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;A user with that username already exists.&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                              help_text&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                              max_length&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;150&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;unique&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                              validators&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[django.contrib.auth.validators.UnicodeUsernameValidator()],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                              verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;username&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;first_name&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.CharField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;blank&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;max_length&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;150&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;first name&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;last_name&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.CharField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;blank&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;max_length&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;150&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;last name&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;email&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.EmailField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;blank&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;max_length&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;254&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;email address&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;is_staff&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.BooleanField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;False&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                 help_text&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Designates whether the user can log into this admin site.&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                 verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;staff status&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;is_active&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.BooleanField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                  help_text&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Designates whether this user should be treated as active. Unselect this instead of deleting accounts.&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                  verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;active&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;date_joined&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.DateTimeField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;django.utils.timezone.now, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;date joined&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;groups&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.ManyToManyField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;blank&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                  help_text&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;The groups this user belongs to. A user will get all permissions granted to each of their groups.&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                  related_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user_set&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;related_query_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;to&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;auth.Group&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                  verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;groups&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user_permissions&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.ManyToManyField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;blank&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;help_text&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Specific permissions for this user.&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                            related_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user_set&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;related_query_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                            to&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;auth.Permission&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user permissions&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;            options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;                &apos;db_table&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;auth_user&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;            managers&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;objects&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, django.contrib.auth.models.UserManager()),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        ),&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you followed &lt;strong&gt;steps 1-5&lt;/strong&gt;, your migrations file should now look something like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Generated by Django 3.2.5 on 2021-07-12 16:28&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; django.contrib.auth.models&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; django.contrib.auth.validators&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; django.utils.timezone&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; django.db &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; migrations, models&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Migration&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;migrations&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Migration&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    initial &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; True&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    dependencies &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;auth&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;0012_alter_user_first_name_max_length&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    operations &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        migrations.CreateModel(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;            name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;FakeModel&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;            fields&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;id&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.BigAutoField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;auto_created&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;primary_key&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;serialize&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;False&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;ID&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        ),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        migrations.CreateModel(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;            name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;User&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;            fields&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;id&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.BigAutoField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;auto_created&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;primary_key&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;serialize&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;False&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;ID&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;password&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.CharField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;max_length&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;128&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;password&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;last_login&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.DateTimeField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;blank&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;last login&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;is_superuser&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.BooleanField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;False&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                     help_text&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Designates that this user has all permissions without explicitly assigning them.&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                     verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;superuser status&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;username&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.CharField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;error_messages&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;unique&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;A user with that username already exists.&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                              help_text&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                              max_length&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;150&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;unique&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                              validators&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[django.contrib.auth.validators.UnicodeUsernameValidator()],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                              verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;username&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;first_name&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.CharField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;blank&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;max_length&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;150&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;first name&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;last_name&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.CharField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;blank&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;max_length&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;150&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;last name&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;email&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.EmailField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;blank&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;max_length&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;254&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;email address&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;is_staff&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.BooleanField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;False&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                 help_text&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Designates whether the user can log into this admin site.&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                 verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;staff status&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;is_active&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.BooleanField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                  help_text&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Designates whether this user should be treated as active. Unselect this instead of deleting accounts.&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                  verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;active&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;date_joined&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.DateTimeField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;django.utils.timezone.now, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;date joined&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;groups&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.ManyToManyField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;blank&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                  help_text&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;The groups this user belongs to. A user will get all permissions granted to each of their groups.&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                  related_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user_set&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;related_query_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;to&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;auth.Group&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                  verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;groups&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user_permissions&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, models.ManyToManyField(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;blank&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;help_text&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Specific permissions for this user.&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                            related_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user_set&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;related_query_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                                                            to&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;auth.Permission&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;verbose_name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user permissions&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;            options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;                &apos;db_table&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;auth_user&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;            managers&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;objects&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, django.contrib.auth.models.UserManager()),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        ),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;8-enable-the-custom-user-model-in-settings&quot;&gt;8. Enable the custom user model in settings&lt;/h1&gt;
&lt;p&gt;In your &lt;code&gt;settings.py&lt;/code&gt; file, add:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;AUTH_USER_MODEL&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;accounts.User&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Remember to change &lt;code&gt;accounts&lt;/code&gt; to your app name!&lt;/p&gt;
&lt;h1 id=&quot;9-create-an-empty-migration&quot;&gt;9. Create an empty migration&lt;/h1&gt;
&lt;p&gt;Now, we need to make one last migration in order to finalize the move to the new custom User model. Execute:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;python3&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; manage.py&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; makemigrations&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --empty&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; accounts&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --name&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; change_user_type&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;while substituting &lt;code&gt;accounts&lt;/code&gt; with your app name. Django should create a new empty migration for you. For context, my migration was in &lt;code&gt;accounts/migrations/0002_change_user_type.py&lt;/code&gt;.&lt;/p&gt;
&lt;h1 id=&quot;10-edit-the-empty-migration&quot;&gt;10. Edit the empty migration&lt;/h1&gt;
&lt;p&gt;Add the following function to the top of your migrations file (below the imports):&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; change_user_type&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(apps, schema_editor):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ContentType &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; apps.get_model(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;contenttypes&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;ContentType&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ct &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ContentType.objects.filter(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;        app_label&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;auth&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;        model&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ).first()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ct:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        ct.app_label &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;user&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        ct.save()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, under &lt;code&gt;operations&lt;/code&gt;, add:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      migrations.RunPython(change_user_type),&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you’re done, your migrations file should look something like this (with the &lt;code&gt;dependencies&lt;/code&gt; section being the only difference if you used your own app with many more migrations):&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Generated by Django 3.2.5 on 2021-07-12 16:38&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; django.db &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; migrations&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; change_user_type&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(apps, schema_editor):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ContentType &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; apps.get_model(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;contenttypes&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;ContentType&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ct &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ContentType.objects.filter(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;        app_label&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;auth&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;        model&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;user&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ).first()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ct:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        ct.app_label &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;user&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        ct.save()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Migration&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;migrations&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Migration&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    dependencies &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;accounts&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;0001_initial&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    operations &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        migrations.RunPython(change_user_type),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;11-migrate-to-new-db_table-field&quot;&gt;11. Migrate to new &lt;code&gt;db_table&lt;/code&gt; field&lt;/h1&gt;
&lt;p&gt;You may now want to generate a migration to migrate to the default table. Edit &lt;code&gt;models.py&lt;/code&gt; and remove the &lt;code&gt;Meta&lt;/code&gt; class. If your User model is empty, make sure to add &lt;code&gt;pass&lt;/code&gt; as well. Your User model should then look something like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; User&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AbstractUser&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    pass&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Generate a new migration.&lt;/p&gt;
&lt;p&gt;If you skipped to &lt;strong&gt;step 6&lt;/strong&gt; from &lt;strong&gt;step 1&lt;/strong&gt;, you’re done at this point. Just push a new release and let admins upgrade. Future migrations can be generated and applied just like before.&lt;/p&gt;
&lt;h1 id=&quot;12-remove-the-temporary-fake-models&quot;&gt;12. Remove the temporary fake models&lt;/h1&gt;
&lt;p&gt;If you followed &lt;strong&gt;steps 1-5&lt;/strong&gt;, we need to remove the fake model we created to generate an initial migration. Edit &lt;code&gt;models.py&lt;/code&gt; and remove the &lt;code&gt;FakeModel&lt;/code&gt; model.&lt;/p&gt;
&lt;p&gt;Generate a new migration, and deploy. Remember to make an announcement so that server admins do not forget to upgrade to the go-between release first and perform a database migration.&lt;/p&gt;
&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;I tested the steps above with my own Django project, which uses PostgreSQL for the backend and Docker for deploy orchestration. The migration went without a hitch and I was able to keep my existing database tables without any dirty modifications.&lt;/p&gt;
&lt;p&gt;Let me know how the steps above worked out for you in the comments below!&lt;/p&gt;
&lt;h1 id=&quot;credits&quot;&gt;Credits&lt;/h1&gt;
&lt;p&gt;This blog post is based largely on &lt;a href=&quot;https://www.caktusgroup.com/blog/2019/04/26/how-switch-custom-django-user-model-mid-project/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;this blog post from Caktus Group&lt;/a&gt; and &lt;a href=&quot;https://code.djangoproject.com/ticket/25313&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;the bug report on Django’s bugtracker&lt;/a&gt;, specifically &lt;a href=&quot;https://code.djangoproject.com/ticket/25313#comment:24&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;comment 24&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title>Minecraft LAN advertising</title><link>https://ericswpark.com/blog/2021/2021-06-25-minecraft-lan-advertising/</link><guid isPermaLink="true">https://ericswpark.com/blog/2021/2021-06-25-minecraft-lan-advertising/</guid><pubDate>Fri, 25 Jun 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;While looking for a way to “announce” a Minecraft server to local players, I found &lt;a href=&quot;https://void7.net/advertising-linux-minecraft-servers-to-the-lan/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;this article written 7 years ago&lt;/a&gt;. The included Python script works with a couple of tweaks to make it compatible with Python 3:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; socket&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; time&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;servers &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;motd1&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;25565&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;motd2&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;25566&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;BROADCAST_IP&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;255.255.255.255&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;BROADCAST_PORT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 4445&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sock &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; socket.socket(socket.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;AF_INET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, socket.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;SOCK_DGRAM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sock.setsockopt(socket.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;SOL_SOCKET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, socket.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;SO_BROADCAST&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Broadcasting Minecraft servers to LAN&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;while&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; server &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; servers:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                msg &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;[MOTD]&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%s&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;[/MOTD][AD]&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%d&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;[/AD]&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; %&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (server[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;], server[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                sock.sendto(msg.encode(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;UTF-8&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;), (&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;BROADCAST_IP&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;BROADCAST_PORT&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        time.sleep(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1.5&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You only need to change the server array, defined at the top of the file. Save to a file like &lt;code&gt;broadcast_to_lan.py&lt;/code&gt;, and run with &lt;code&gt;python3 broadcast_to_lan.py&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Thanks to kebian for the original script!&lt;/p&gt;</content:encoded></item><item><title>Why I returned the iPad Smart Keyboard</title><link>https://ericswpark.com/blog/2021/2021-06-24-why-i-returned-the-ipad-smart-keyboard/</link><guid isPermaLink="true">https://ericswpark.com/blog/2021/2021-06-24-why-i-returned-the-ipad-smart-keyboard/</guid><pubDate>Thu, 24 Jun 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;After spending about five minutes with the iPad Smart Keyboard, I decided to ship it back to Apple for a refund. Here’s why.&lt;/p&gt;
&lt;p&gt;(The Smart Keyboard I got was for my iPad Air 3.)&lt;/p&gt;
&lt;h1 id=&quot;quality&quot;&gt;Quality&lt;/h1&gt;
&lt;p&gt;When I first opened the package, I thought I had received a fake keyboard.&lt;/p&gt;
&lt;p&gt;The “case” part of the Smart Keyboard was fine - it matched my original iPad Smart Cover’s feel and look. The material of the inner and outer parts of the “case” was identical to the original Smart Cover.&lt;/p&gt;
&lt;p&gt;However, the “keyboard” part of the Smart Keyboard had terrible quality. I actually thought it was some sort of cheap imitation of the actual product - and then realized it &lt;strong&gt;was&lt;/strong&gt; the real deal from Apple after looking at review images online. The keys are draped with this cloth-like material, which supposedly keeps out dust, but it looks and feels cheap.&lt;/p&gt;
&lt;p&gt;Seriously. I regret not taking photos, but look up images for the Smart Keyboard online and look at the keyboard portion. The cloth-like material has a very weird texture to it, which makes it look cheap and flimsy.&lt;/p&gt;
&lt;h1 id=&quot;stability&quot;&gt;Stability&lt;/h1&gt;
&lt;p&gt;Okay, the keyboard quality sucks. But maybe the keyboard works better than regular Bluetooth keyboards. After all, it connects using the connector on the iPad. It should be better, right? Right?&lt;/p&gt;
&lt;p&gt;Nope. For the first couple of moments after attaching the keyboard, everything was fine. I could type normally and the keyboard responded properly. Then I noticed my iPad was too slanted, which caused reflections on the screen. No problem, just readjust the stand so that the iPad leans forward a bit more, and… the keyboard connection dropped.&lt;/p&gt;
&lt;p&gt;For some strange reason, the keyboard refuses to work unless the bottom part of the iPad touches the rubber bar right above the keyboard. I thought the entire point of the Smart Connector was that it could be used at any prop point on the actual cover. So why does the keyboard stop working if I move the iPad two centimeters across to get a better viewing angle?&lt;/p&gt;
&lt;p&gt;This is such a weird issue that I actually think my keyboard was faulty or something. The keyboard would stop working when it was tilted across. Really strange!&lt;/p&gt;
&lt;h1 id=&quot;key-feel&quot;&gt;Key feel&lt;/h1&gt;
&lt;p&gt;Obviously, good keyboards have good “key feel,” where the keys don’t feel too spongy or tacky. While the Smart Keyboard had okay levels of key tactility, the cloth over the keys again hampered the key feel a bit.&lt;/p&gt;
&lt;p&gt;While typing on the Smart Keyboard, I noticed a lot of missed keystrokes or duplicate keys as my fingers struggled to adjust to the weird key feel of the Smart Keyboard. Granted, I only spent five minutes with the keyboard, so it’s possible that I may have adjusted over time. Still, other keyboards provided much better key feel and comfort.&lt;/p&gt;
&lt;p&gt;I realize that packing a good quality keyboard into such limited space like the Smart Keyboard is hard, and therefore the dimensions of the keyboard limit how good the key feel could be. However, the primary purpose of the keyboard is to type on, and if it fails at that one thing, then it’s not a good product in my opinion.&lt;/p&gt;
&lt;h1 id=&quot;missing-keys&quot;&gt;Missing keys&lt;/h1&gt;
&lt;p&gt;Speaking of keys, a lot of essential keys were missing from the keyboard.&lt;/p&gt;
&lt;p&gt;The most important omission, in my opinion, is the Escape key, because it severely hampered development work. I use Blink to SSH into servers, and the lack of an Escape key is a real dealbreaker in many Linux programs because most expect the Escape key to be present on the keyboard, like they should. Any sane keyboard should have an escape key.&lt;/p&gt;
&lt;p&gt;Also, the keyboard lacked any function keys, like volume and brightness control, or the button to go back to the home screen, or to control media playback. Many Bluetooth keyboards have these function keys, which makes using the iPad easier, since you don’t have to reach up to touch the iPad’s screen when adjusting settings or using the volume rocker to the side of the iPad, which is also cumbersome.&lt;/p&gt;
&lt;h1 id=&quot;mobility-and-weight&quot;&gt;Mobility and weight&lt;/h1&gt;
&lt;p&gt;The Smart Keyboard is arguably great because it latches onto your iPad and stays available with it. However, the cost of being constantly attached to your iPad is that your iPad will get bulky to handle and carry around.&lt;/p&gt;
&lt;p&gt;Because the Smart Keyboard attaches magnetically, it becomes harder to use the iPad in standalone mode, where you don’t need a keyboard (say, for watching Netflix). With Bluetooth keyboards, you can just pick up your iPad, whereas with the Smart Keyboard, you need to first wrestle the iPad out of the magnetic grip. Otherwise, you’re left holding a heavy iPad with the keyboard folded across the back.&lt;/p&gt;
&lt;p&gt;I was hoping that the Smart Keyboard would be at least lighter compared to my Logitech K380, but after comparing the two in my hand I didn’t feel a significant difference. And because the Logitech keyboard is not magnetically attached to the iPad, I found it easier to use.&lt;/p&gt;
&lt;h1 id=&quot;price&quot;&gt;Price&lt;/h1&gt;
&lt;p&gt;The Smart Keyboard costs ₩199,000 here in South Korea. In comparison, the K380 costs ₩34,500, and cheaper Bluetooth keyboards retail for about ₩21,670. That’s a crazy difference. You can buy multiple Bluetooth keyboards, use them for a very long time, and still have money left over for coffee to fuel your writing sessions.&lt;/p&gt;
&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Judging with all of the points listed above, it’s a no-brainer: the Smart Keyboard doesn’t make sense &lt;strong&gt;at all&lt;/strong&gt;. If the Smart Keyboard excelled at even just one point listed above, then an argument could be made that it’s worth it because it’s a first-party product. However, the Smart Keyboard has too many cons for it to be usable, and thus is not a good buy compared to other keyboards on the market, even if they are third-party solutions.&lt;/p&gt;</content:encoded></item><item><title>Speeding up invisible video players</title><link>https://ericswpark.com/blog/2021/2021-06-22-speeding-up-invisible-video-players/</link><guid isPermaLink="true">https://ericswpark.com/blog/2021/2021-06-22-speeding-up-invisible-video-players/</guid><pubDate>Tue, 22 Jun 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When watching videos online, I always make sure to change the video speed to 2x if the player exposes such a control. It’s become a habit, since I can still (usually) understand everything within a video at 2x the speed, saving time.&lt;/p&gt;
&lt;p&gt;Some players don’t expose video speed control, for whatever reason. That’s okay, because most websites nowadays use HTML5 for video playback, and the playback rate can be adjusted manually using DevTools.&lt;/p&gt;
&lt;p&gt;However, while watching a news segment on Naver today, I went into DevTools to adjust the video playback rate and noticed that DevTools couldn’t find the video tag. Time to investigate!&lt;/p&gt;
&lt;h1 id=&quot;how-do-you-adjust-playback-speed-in-devtools&quot;&gt;How do you adjust playback speed in DevTools?&lt;/h1&gt;
&lt;p&gt;With either:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;document.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;querySelector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;video&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;).defaultPlaybackRate &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2.0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;document.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;getElementsByTagName&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;video&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;].playbackRate &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;(Notice the second way of speeding up the video has an extra &lt;code&gt;[0]&lt;/code&gt; as a parameter. This is because &lt;code&gt;getElementsByTagName()&lt;/code&gt; returns an array of elements (even if there is one element in the array) so we need to access the element in the array using &lt;code&gt;[0]&lt;/code&gt; (get the first item in the array).)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This works 95% of the time, in my experience. But on Naver’s website, the two commands only return errors about undefined elements. So what’s going on with Naver’s site?&lt;/p&gt;
&lt;h1 id=&quot;testing-testing&quot;&gt;Testing, testing&lt;/h1&gt;
&lt;p&gt;Here’s a &lt;a href=&quot;https://news.naver.com/main/read.nhn?oid=056&amp;#x26;aid=0011068640&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;sample news segment that you can use to follow along.&lt;/a&gt; I’ll use this article in this post to show you what happened. You can try the two commands above right now if you want - they’ll spit out errors about undefined elements and won’t work.&lt;/p&gt;
&lt;p&gt;You can see that the video playback starts when we hit the play button. So let’s jump into DevTools and look for the video tag.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;no-video-tag&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1942&quot; height=&quot;202&quot; src=&quot;/_astro/no-video-tag.Dto8DGRH_Zj29k5.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;Well, that’s odd! Using the picker tool to find the video doesn’t work either, because it focuses on the &lt;code&gt;&amp;#x3C;div&gt;&lt;/code&gt; that covers the video player. Grr!&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;overlay-div&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;2022&quot; height=&quot;1304&quot; src=&quot;/_astro/overlay-div.DiZYD7lL_Z1kpmWs.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;Time to find it manually, then. I combed through the HTML file until I found the tag, buried within a thousand &lt;code&gt;&amp;#x3C;div&gt;&lt;/code&gt; tags:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;video-tag-found&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;2072&quot; height=&quot;1472&quot; src=&quot;/_astro/video-tag-found.DXHSqYH__1wxnzo.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;Hmm, the video tag exists, but the find tool can’t find it. Why?&lt;/p&gt;
&lt;p&gt;Let’s save the entire page as an HTML file, and then use a text editor and see if we can find the tag:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;text-editor-fail&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1418&quot; height=&quot;546&quot; src=&quot;/_astro/text-editor-fail.5usy13Yl_Z10uJg.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;(Ignore the broken Korean text - this is probably because the file opened up the UTF-8 encoding instead of the proper EUC-KR encoding. As is shown above, the HTML tags survived.)&lt;/p&gt;
&lt;p&gt;Still no! So we know now that this video player is loaded dynamically using JavaScript. My leading theory is that the browser can’t find the video element because it’s added to the DOM after the page completely loads.&lt;/p&gt;
&lt;p&gt;But wait! Shouldn’t you be able to access the item within the console since the console executes after the page has fully loaded?&lt;/p&gt;
&lt;h1 id=&quot;safari-time&quot;&gt;Safari time&lt;/h1&gt;
&lt;p&gt;I use Firefox as my default browser. What if this is a bug with Firefox?&lt;/p&gt;
&lt;p&gt;Let’s test with Safari. Loading up the page and using Ctrl-F within DevTools:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;safari-finds&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1328&quot; height=&quot;396&quot; src=&quot;/_astro/safari-finds.NNPOvAk1_Z2uC5EN.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;Wow, Safari actually found the element! Let’s see now if the command will work:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;safari-commands&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;962&quot; height=&quot;270&quot; src=&quot;/_astro/safari-commands.DWhMxZWM_Z1bOsb3.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;The first command executed without any undefined errors, but didn’t have any effect on the video player (it still played at normal speed). The second command worked, however, and the video began to play back at 2x speed!&lt;/p&gt;
&lt;p&gt;So did we uncover a bug with Firefox?&lt;/p&gt;
&lt;h1 id=&quot;context-people-context&quot;&gt;Context, people, context&lt;/h1&gt;
&lt;p&gt;Let’s go back to the Firefox console. What did we miss? Well, we missed the context in which the JavaScript debugging console ran in.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;firefox-context-button&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;3358&quot; height=&quot;1052&quot; src=&quot;/_astro/firefox-context-button.iunLsQKh_ZREFku.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;Because the website had multiple frames, Firefox decided to choose the first frame to execute JavaScript commands within. However, our video element was within another context/frame, so it wasn’t being picked up by the JavaScript console.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;firefox-second-context&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;3358&quot; height=&quot;1052&quot; src=&quot;/_astro/firefox-second-context.BYpHXhpi_2bp2on.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;After choosing the correct context, the command listed above correctly picked up the video element and let me speed it up. Yay!&lt;/p&gt;
&lt;h1 id=&quot;what-did-we-learn-today&quot;&gt;What did we learn today?&lt;/h1&gt;
&lt;p&gt;We learned that JavaScript in DevTools can run under different contexts, and that sometimes elements are cordoned off to specific contexts.&lt;/p&gt;
&lt;p&gt;One area where Firefox can improve is to check other contexts if the &lt;code&gt;getElement*()&lt;/code&gt; function fails to find elements within the current context, and ask the developer if they want to switch to a different context. Also, the button should be changed to a dropdown that clearly shows what context we are currently in.&lt;/p&gt;
&lt;p&gt;Also, Firefox seems to have a bug where it can’t find HTML elements added to the page by scripts after the page finishes loading. Seeing as the element is correctly detected on Safari DevTools, this seems like a Firefox bug.&lt;/p&gt;
&lt;p&gt;(P.S. Naver should really, really add speed controls to their video players.)&lt;/p&gt;</content:encoded></item><item><title>C pointer double-free fix on macOS</title><link>https://ericswpark.com/blog/2021/2021-04-13-c-pointer-double-free-fix-on-macos/</link><guid isPermaLink="true">https://ericswpark.com/blog/2021/2021-04-13-c-pointer-double-free-fix-on-macos/</guid><pubDate>Tue, 13 Apr 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;While debugging a C program I wrote today, I kept getting this error:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;main(44466,0x10ae7ce00) malloc: *** error for object 0x7000000000000000: pointer being freed was not allocated&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;main(44466,0x10ae7ce00) malloc: *** set a breakpoint in malloc_error_break to debug&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;zsh: abort      ./main test_cases/test_1.html output.txt&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For some strange reason, using &lt;code&gt;lldb&lt;/code&gt; I was unable to reproduce the issue. Or that’s what I thought - after running the program on &lt;code&gt;lldb&lt;/code&gt; multiple times, it actually stopped at the &lt;code&gt;malloc_error_break&lt;/code&gt; breakpoint I set.&lt;/p&gt;
&lt;p&gt;Unfortunately, the stack trace that &lt;code&gt;lldb&lt;/code&gt; gave me didn’t really say exactly what line caused the double-free. I tried running the program on &lt;code&gt;gdb&lt;/code&gt;, but the program refused to crash on the Linux instance. When I forced a crash by repeatedly running the program over and over, the stack trace there was also unhelpful, and &lt;code&gt;valgrind&lt;/code&gt; didn’t report anything particularly interesting.&lt;/p&gt;
&lt;p&gt;Well, on macOS, &lt;code&gt;clang&lt;/code&gt; (LLVM compiler) comes with &lt;a href=&quot;https://clang.llvm.org/docs/AddressSanitizer.html&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;AddressSanitizer&lt;/a&gt;, which can be used to figure out exactly where the program is crashing. So I re-compiled my program using &lt;code&gt;clang&lt;/code&gt; with the AddressSanitizer flag enabled:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;clang&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -g&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -fsanitize=address&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; main_debug&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; main.c&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (... &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;rest&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; the&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; source&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; files&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then ran the program. The AddressSanitizer immediately showed me the stack trace I was looking for:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ericswpark@Erics-MacBook-Pro Project % ./main_debug test_cases/test_1.html debug_output.txt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;AddressSanitizer:DEADLYSIGNAL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;=================================================================&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;==44732==ERROR: AddressSanitizer: SEGV on unknown address (pc 0x000102e04b98 bp 0x7ffeece1ba20 sp 0x7ffeece1b9f0 T0)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;==44732==The signal is caused by a READ memory access.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;==44732==Hint: this fault was caused by a dereference of a high value address (see register values below).  Dissassemble the provided pc to learn which register was used.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    #0 0x102e04b98 in __asan::Allocator::Deallocate(void*, unsigned long, unsigned long, __sanitizer::BufferedStackTrace*, __asan::AllocType)+0x48 (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x6b98)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    #1 0x102e4732a in wrap_free+0x10a (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x4932a)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    #2 0x102de8131 in html_tag_free main.c:278&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    #3 0x102de7f7c in stack_pop main.c:298&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    #4 0x102de79e5 in main main.c:219&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    #5 0x7fff203b9620 in start+0x0 (libdyld.dylib:x86_64+0x15620)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;==44732==Register values:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;rax = 0x0000000000000002  rbx = 0xbebebebebebebebe  rcx = 0x0000000000000003  rdx = 0x0000000000000000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;rdi = 0xbebebebebebebebe  rsi = 0xbebebebebebebebe  rbp = 0x00007ffeece1ba20  rsp = 0x00007ffeece1b9f0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; r8 = 0x00007ffeece1ba30   r9 = 0x0000000000000001  r10 = 0xffffffffffffffff  r11 = 0x00000fffffffffff&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;r12 = 0x0000000000000001  r13 = 0x0000000000000000  r14 = 0x00007ffeece1ba30  r15 = 0x0000000102ea2d40&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;AddressSanitizer can not provide additional info.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;SUMMARY: AddressSanitizer: SEGV (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x6b98) in __asan::Allocator::Deallocate(void*, unsigned long, unsigned long, __sanitizer::BufferedStackTrace*, __asan::AllocType)+0x48&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;==44732==ABORTING&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;zsh: abort      ./main_debug test_cases/test_1.html debug_output.txt&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This showed me the error was on line 278 of my &lt;code&gt;main.c&lt;/code&gt; file. Sure enough, the problem was because I wasn’t setting one of the character arrays in a struct variable to &lt;code&gt;NULL&lt;/code&gt; when I was creating it. The following code block:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;c&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (var&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;chararr)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    free&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(var-&gt;chararr);&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;     // error here&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    var-&gt;chararr &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; NULL&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;checked to see if &lt;code&gt;var-&gt;chararr&lt;/code&gt; was &lt;code&gt;NULL&lt;/code&gt;, but since I didn’t set it to &lt;code&gt;NULL&lt;/code&gt; on initialization, went ahead and &lt;code&gt;free&lt;/code&gt;d whatever poor address space that ended up in that pointer.&lt;/p&gt;
&lt;p&gt;So the next time you run into a weird memory bug with your program, use AddressSanitizer included with &lt;code&gt;clang&lt;/code&gt; to figure out exactly what part of your program is the problem!&lt;/p&gt;</content:encoded></item><item><title>Google Photos and iCloud Photos</title><link>https://ericswpark.com/blog/2021/2021-03-07-google-photos-and-icloud-photos/</link><guid isPermaLink="true">https://ericswpark.com/blog/2021/2021-03-07-google-photos-and-icloud-photos/</guid><pubDate>Sun, 07 Mar 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Here are some of the things I learned while using Google/iCloud Photos:&lt;/p&gt;
&lt;h1 id=&quot;moving-from-icloud-photos-to-google-photos&quot;&gt;Moving from iCloud Photos to Google Photos&lt;/h1&gt;
&lt;p&gt;This is really easy if you still have your iDevice around.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Turn off iCloud Photos. When asked, select to download all photos/videos back to your device in original quality. If you don’t do this, photos/videos that you upload to Google Photos will be the “optimized” version, which means it will look like crap.&lt;/li&gt;
&lt;li&gt;Download Google Photos.&lt;/li&gt;
&lt;li&gt;Turn on Google Photos backup and wait until all the photos/videos are backed up to the cloud.&lt;/li&gt;
&lt;li&gt;Once done, uninstall Google Photos and delete your photos/videos from iCloud Photos if you wish.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&quot;the-other-way&quot;&gt;The other way&lt;/h1&gt;
&lt;p&gt;Unfortunately, going the other way around is a difficult process, which is really, really ironic.&lt;/p&gt;
&lt;h1 id=&quot;dont-use-google-takeout&quot;&gt;Don’t use Google Takeout&lt;/h1&gt;
&lt;p&gt;My first attempt at moving photos/videos from Google Photos to iCloud Photos involved Google Takeout to first export the photos. Unfortunately, Google Takeout is a steaming pile of garbage, in that it actually discards all of the media metadata when exporting your archive.&lt;/p&gt;
&lt;p&gt;Well, not quite discard. Rather, they’ll “split” the metadata into a separate &lt;code&gt;.json&lt;/code&gt; file that sits next to your original file with the same file name. And frustratingly, some photos and videos do in fact preserve metadata - while still having a separate &lt;code&gt;.json&lt;/code&gt; file outlining the duplicate metadata. For other photos and videos, the metadata is mangled, and the date of the metadata is set to the archival date.&lt;/p&gt;
&lt;p&gt;&lt;del&gt;Sure, I could probably make a script to “merge” the metadata back into the media files.&lt;/del&gt; Don’t need to, see below. But why does Google do this metadata-stripping in the first place? What about people that can’t write up scripts when they export their files? This is horrible!&lt;/p&gt;
&lt;p&gt;Another catch with this method is that it won’t preserve some “live photos” - although this is probably more of an Apple bug than a Google bug. In short, if your photos are in JPG/PNG format and your “live photos” video is in MP4, then it will merge cleanly back into a “live photo” once you import the two files back into the Photos app on your Mac. &lt;strong&gt;However,&lt;/strong&gt; if your photo is in a HEIC/HEIF format, then Photos will create two separate entries and not merge the photos and videos back into a “live photo.”&lt;/p&gt;
&lt;p&gt;So if you’re the poor soul who didn’t know any of these gotchas and used Google Takeout to import your photos and videos into iCloud, you’re in for a very nasty surprise. When I tried this method, my photo library got completely messed up - some of the photos surfaced to the top because of the incorrect date set in the metadata, and a lot of duplicate entries were created because of the HEIC/HEIF bug.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;: Apparently, there already &lt;a href=&quot;https://github.com/TheLastGimbus/GooglePhotosTakeoutHelper&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;is a Python script that merges the metadata for you&lt;/a&gt;. Unfortunately, it doesn’t support HEIC/HEIF, so my point still stands - don’t use Google Takeout.&lt;/p&gt;
&lt;h1 id=&quot;moving-from-google-photos-to-icloud-photos&quot;&gt;Moving from Google Photos to iCloud Photos&lt;/h1&gt;
&lt;p&gt;With that, here is the actual process of moving from Google Photos to iCloud Photos:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Grab an iDevice and turn on iCloud Photos.&lt;/li&gt;
&lt;li&gt;Download Google Photos.&lt;/li&gt;
&lt;li&gt;When asked, do &lt;strong&gt;not&lt;/strong&gt; enable automatic backup.&lt;/li&gt;
&lt;li&gt;Hold your finger on an entry for 2 seconds and then drag down to select multiple items. &lt;strong&gt;Do NOT select more than a couple files!&lt;/strong&gt; Or else, the download will hang and never complete. Also, do not mix file types, as that will also cause the download to hang. Yes, the Google Photos app sucks.&lt;/li&gt;
&lt;li&gt;Once you have selected the media to export, click on the “Share” button and then select “save to device.” Weirdly, unlike the web version, there is no “Download” option under the 3-dot menu.&lt;/li&gt;
&lt;li&gt;Google Photos will start saving your photos/videos to your device.&lt;/li&gt;
&lt;li&gt;Once the downloads are all done, uninstall Google Photos. &lt;strong&gt;Do NOT delete the photos on the Google Photos app, as doing so will wipe the photos from your device and subsequently iCloud Photos as well!&lt;/strong&gt; Instead, if you want to do a full migration and delete photos from Google Photos, then delete the app first, and then log into the web interface and delete the photos that way.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This method, unlike Google Takeout, will preserve the correct metadata and will also merge together all live photos properly.&lt;/p&gt;
&lt;p&gt;Unfortunately, the Google Photos app also has a couple of bugs here. First, I noticed the download and export process is horribly buggy and painfully slow. Every couple downloads, I would get a “Trouble Saving. Try again later” message. (This usually happens if I mix file types - as in, regular photos, live photos, videos, panoramas, burst shots, etc.) Now, that would be okay if Google Photos really couldn’t save my photos - I could just try again. Unfortunately, that error is a lie as well, as Google Photos does save a couple of photos from the selection before quitting. So downloading the same selection results in duplicates.&lt;/p&gt;
&lt;p&gt;The download progress bar doesn’t help at all during the download, as it swings back and forth wildly as it moves between the different files. Sometimes, it gets stuck and never moves, only to abruptly finish or give me an error message. Why not make a progress bar with the total set to the size of the selected photos and not each photo? I don’t care if the progress bar is not linear, but it shouldn’t go backwards, at least!&lt;/p&gt;
&lt;p&gt;And like I said above, the download process is slow. Sometimes, it hangs. When that happens, I need to cancel, and check which photos Google Photos felt like downloading during each attempt, and then deselect those before restarting the process.&lt;/p&gt;
&lt;p&gt;The second bug I came across: any burst photos you may have in your library will not be saved. To save burst photos, you have to open the photo, and then select a frame, and then hit download, and then select another frame, and then hit download… and do this for all of the frames of the burst shot. The burst shot will show correctly in the Photos app, but this is seriously annoying!&lt;/p&gt;
&lt;p&gt;The last bug I encountered is probably the nastiest one: &lt;strong&gt;Google Photos will silently corrupt some HEIC/HEIF photos.&lt;/strong&gt; This is why the checklist above mentions turning off backup and sync. If backup and sync was on and a corrupted version was saved locally, then you would have been absolutely screwed if Google re-uploaded the corrupted file. I noticed that this bug mostly occurs if the photo was edited before. If a file becomes corrupt, it will show up as an “empty” photo on the Photos app, and there will be an exclamation mark at the bottom right corner of the photo. Tapping it will produce the following message: “An error occurred while loading a higher-quality version of this photo.” Unfortunately, re-downloading from Google Photos will not fix this problem.&lt;/p&gt;
&lt;p&gt;Fortunately, I did find a workaround - launch the Photos app on macOS, right-click on the photo, and then select “Revert to original.” This removes any modifications you may have made, but at least the photo becomes normal again. Wait for it to sync back over to your other iDevices.&lt;/p&gt;
&lt;p&gt;Why, Google Photos, why?!&lt;/p&gt;
&lt;h1 id=&quot;which-one-is-better&quot;&gt;Which one is better?&lt;/h1&gt;
&lt;p&gt;I had the opportunity to use both services, since I frequently go back and forth between iOS and Android for app development. I thought Google Photos would just beat iCloud Photos, given that it’s available on both Android and iOS, but after that horrible experience exporting photos, now I’m not so sure.&lt;/p&gt;
&lt;p&gt;Most of my family members use Apple devices, so really it’s just a matter of not wanting to pay extra for two services. I think I’ll probably go back to iCloud Photos and keep Google Photos around as a sync solution to transfer photos/videos taken on Android devices back to iCloud Photos. The Google One membership isn’t very expensive, but in Korea the membership doesn’t even bundle other Google services, so it makes more sense to cancel it and use the 15 GB of storage as a temporary offloading space.&lt;/p&gt;
&lt;p&gt;In my opinion, you will most likely be stuck with whatever option you choose now, as these services will probably get more and more restrictive about users leaving as time goes on. And if your entire family uses one service, then the gargantuan process of getting everybody to move across to another platform will be extremely daunting.&lt;/p&gt;
&lt;p&gt;I heard Google Photos has more features regarding photo/video management, and that kind of matched my experience. But all the while I was using Google Photos, I didn’t really like the idea of letting Google take care of my photos library. I know I’m probably just being paranoid, but Google is an advertising company first after all, and letting them train their AI models on my photos/videos just doesn’t feel right.&lt;/p&gt;
&lt;p&gt;I know moving to iCloud Photos is just shifting the problem to Apple, and hoping they won’t go manic one day and do a 180 on their privacy stance, but I just feel more comfortable on iCloud than on Google Photos. I’m not really sure why. And obviously, some people will not care about this at all - after all, if it backs up photos and videos in case you break your phone who would say no?&lt;/p&gt;
&lt;p&gt;Personally, I would still recommend iCloud Photos, because it’s just really easy to back up iCloud Photos. Yes, you should back up your photo library, even if it is being synced to an online service. If you don’t have multiple copies of something, you don’t have it backed up. Worst case scenario, every datacenter owned by Apple and Google go poof and you should still have access to your photos. With iCloud Photos, Time Machine automatically backs up your entire photo library as long as you check “Keep originals” in the Photos app. With Google, there is no backup option. There is no desktop app, and you’ve seen how messy Google Takeout is at exporting all of your original photos. I think this is the one aspect where iCloud beats the pants off of Google Photos, and the one aspect that really, really matters to people that want to keep the photos in their original condition.&lt;/p&gt;
&lt;p&gt;All in all, it’s a toss up. Choose wisely because switching later will be hard.&lt;/p&gt;
&lt;h1 id=&quot;can-i-use-both&quot;&gt;Can I use both?&lt;/h1&gt;
&lt;p&gt;Yes, but do a bit of setup first:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Only install Google Photos on &lt;strong&gt;one&lt;/strong&gt; of your iDevices. Or, only activate the backup feature on &lt;strong&gt;one&lt;/strong&gt; of your iDevices.&lt;/li&gt;
&lt;li&gt;On that same iDevice that you activated the backup feature on, make sure to go into iCloud settings and select “Keep Original.” If you use the “optimized” option, then the photos and videos uploaded to Google Photos will look like crap.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The caveat here is that photos you take on Android or upload to Google Photos won’t automatically transfer over to iCloud Photos. So you’ll have to manually download new media on Google Photos every once in a while.&lt;/p&gt;
&lt;p&gt;If you disable the backup feature on Google Photos, then the reverse won’t work as well, but the media that were previously uploaded will stay on Google Photos. Obviously, you won’t be able to view your photos/videos on iCloud on Android. On desktops at least, you can use the web browser to access iCloud, but the interface is still a bit clunky in my opinion compared to a regular native app.&lt;/p&gt;</content:encoded></item><item><title>Things to do before sharing videos</title><link>https://ericswpark.com/blog/2021/2021-02-28-things-to-do-before-sharing-videos/</link><guid isPermaLink="true">https://ericswpark.com/blog/2021/2021-02-28-things-to-do-before-sharing-videos/</guid><pubDate>Sun, 28 Feb 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h1 id=&quot;compress-the-video&quot;&gt;Compress the video&lt;/h1&gt;
&lt;p&gt;A popular chat app in Korea, KakaoTalk, limits video size to 300 MB or less. A video I was trying to send was about 418 MB with a resolution of 4K. Unfortunately, KakaoTalk won’t automatically convert your video to fit the size limitation, which is a shame, but thankfully &lt;code&gt;ffmpeg&lt;/code&gt; can help you compress your video.&lt;/p&gt;
&lt;p&gt;Run the following:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ffmpeg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -i&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; video.mp4&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -crf&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 24&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; output.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Experiment with the number after the &lt;code&gt;-crf&lt;/code&gt; flag. &lt;code&gt;24&lt;/code&gt; reduced my 418 MB video down to about 122 MB. Higher values will create smaller video files but reduce quality, and vice versa.&lt;/p&gt;
&lt;h1 id=&quot;scale-down-the-video&quot;&gt;Scale down the video&lt;/h1&gt;
&lt;p&gt;If that’s not enough, you can scale down your video to decrease the file size. Use:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ffmpeg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -i&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; video&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -vf&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; scale=xxx:xxx&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; output.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where &lt;code&gt;xxx&lt;/code&gt; denotes the video size. Make sure the original video dimension is divisible by this value. For example, my video is a 3840 by 2160 video. To scale it down to a 1080p video, I would use the dimensions 1920 by 1080 (divide by half). My &lt;code&gt;ffmpeg&lt;/code&gt; parameter would therefore be &lt;code&gt;scale=1920:1080&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;After the resize, the video size dropped down to 36 MB, which is more than acceptable for my needs!&lt;/p&gt;
&lt;h1 id=&quot;strip-location-metadata&quot;&gt;Strip location metadata&lt;/h1&gt;
&lt;p&gt;If you shot your video on your phone or a GPS-enabled camera, then your video may have location metadata embedded. You don’t want to leak your home address or anything like that if you’re sharing your video on the Internet.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ffmpeg&lt;/code&gt; also has a flag to strip out the location metadata (seriously, &lt;code&gt;ffmpeg&lt;/code&gt; developers are awesome!) Run the following:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ffmpeg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -i&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; video.mp4&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -metadata&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; location=&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -metadata&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; location-eng=&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; output.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This strips out the location metadata from your video.&lt;/p&gt;</content:encoded></item><item><title>Persistent access with SSH keys</title><link>https://ericswpark.com/blog/2021/2021-02-01-persistent-access-with-ssh-keys/</link><guid isPermaLink="true">https://ericswpark.com/blog/2021/2021-02-01-persistent-access-with-ssh-keys/</guid><pubDate>Mon, 01 Feb 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Picture this: you got your new machine set up with your development tools. You fire up a Terminal, SSH into your work’s server, and… it asks for your password. Panic sets in as you realize that you decommissioned away the old machine, along with the old SSH keys. You must now remember that password to that server - a password you seldom typed in thanks to your SSH keys - or if the SSH daemon installed on that particular server disallows password-based authentication, be forced to do the call-of-shame to your colleague or the administrator of the server to let you back in.&lt;/p&gt;
&lt;p&gt;Humiliating! Embarrassing. And most importantly, annoying.&lt;/p&gt;
&lt;p&gt;So what if there was a way to enable persistent access to a given server or machine? What if you didn’t have to go around updating every single machine with your new SSH key?&lt;/p&gt;
&lt;p&gt;In this blog post we’ll go over how we can do just that!&lt;/p&gt;
&lt;h1 id=&quot;1-github&quot;&gt;1. GitHub&lt;/h1&gt;
&lt;p&gt;The first thing you have to do is make sure that your GitHub security is top-notch. Because anybody who gets access to your GitHub account will have access to all of your servers.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/settings/security&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;So make sure your GitHub account has a strong password, preferably set with a password manager, and &lt;strong&gt;make absolutely sure to enable 2FA&lt;/strong&gt;.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Once you’ve done that, go over to &lt;a href=&quot;https://github.com/settings/keys&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Settings &amp;gt; SSH and GPG keys, and add your SSH keys here&lt;/a&gt;. This is where you will add all of your SSH keys and the new SSH keys you generate whenever you get a new machine or environment to work in. &lt;strong&gt;You only need to update GitHub, and the other machines will follow.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Done? Now let’s move over to the server or machine you want persistent access to.&lt;/p&gt;
&lt;h1 id=&quot;2-script&quot;&gt;2. Script&lt;/h1&gt;
&lt;p&gt;Remote into the target server, and start!&lt;/p&gt;
&lt;p&gt;Make a script, &lt;code&gt;update-ssh.sh&lt;/code&gt;, wherever you like in your home directory. Paste in the following:&lt;/p&gt;
&lt;figure data-astro-cid-gao2j22i&gt; &lt;div data-astro-cid-gao2j22i&gt; &lt;iframe frameborder=&quot;0&quot; id=&quot;astro-gist-8be6357361c1cbb4de0818ea0f0d15ad-llcl6i380ci&quot; data-astro-gist-iframe data-gist-link=&quot;https://gist.github.com/8be6357361c1cbb4de0818ea0f0d15ad.js&quot; data-gist-styles-url=&quot;/styles/gist.css&quot; data-lazy=&quot;true&quot; data-root-margin=&quot;150&quot; data-show-gist-link-on-error=&quot;true&quot; data-astro-cid-gao2j22i&gt;&lt;/iframe&gt; &lt;/div&gt;  &lt;/figure&gt;  &lt;script type=&quot;module&quot; src=&quot;/home/runner/work/ericswpark.github.io/ericswpark.github.io/node_modules/@kotosha/astro-gist/src/Gist.astro?astro&amp;type=script&amp;index=0&amp;lang.ts&quot;&gt;&lt;/script&gt;
&lt;p&gt;Make sure you replace “ericswpark” with your GitHub username.&lt;/p&gt;
&lt;p&gt;So what does this script do? Well, it fetches your public keys from your GitHub profile, and saves them to the &lt;code&gt;authorized_keys&lt;/code&gt; file in your &lt;code&gt;.ssh&lt;/code&gt; directory. Then it sets the relevant permissions to make sure that the SSH daemon will not reject the file, since the daemon will ignore &lt;code&gt;authorized_keys&lt;/code&gt; files that have too open permissions.&lt;/p&gt;
&lt;p&gt;Set the script as executable and try running the script. Afterwards, you will be able to log into the server without actually manually adding your new SSH key.&lt;/p&gt;
&lt;p&gt;But we want to automate this, so that whenever you add a new key on GitHub, it syncs to all of your servers.&lt;/p&gt;
&lt;h1 id=&quot;3-automate&quot;&gt;3. Automate&lt;/h1&gt;
&lt;p&gt;To make this script run periodically, we’ll use &lt;code&gt;crontab&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Don’t know how to use &lt;code&gt;crontab&lt;/code&gt;? Me too! Let’s just go over the basics.&lt;/p&gt;
&lt;p&gt;The basic syntax of &lt;code&gt;crontab&lt;/code&gt; is as follows:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ls&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first column is minutes, which has a range of 0 to 59, and the second column is hours, which has a range from 0 to 23. The third column is the day of month (1 - 31), the fourth column is the month (1 - 12), and the final column is the day of week (Sunday 0 to Saturday 6). A wildcard value matches to all possible values in that range.&lt;/p&gt;
&lt;p&gt;Therefore, the &lt;code&gt;crontab&lt;/code&gt; entry above will run every hour at 20 minutes (so, 2:20 PM, 3:20 PM, and so on).&lt;/p&gt;
&lt;p&gt;There’s also patterns to make &lt;code&gt;crontab&lt;/code&gt; entries repeat (/2 means every 2 units, and so on), but it’s not supported by all Linux distros, so be careful when using them!&lt;/p&gt;
&lt;p&gt;Some enthusiasts will preach about &lt;code&gt;systemd&lt;/code&gt; tasks, but I think &lt;code&gt;cron&lt;/code&gt; is more prevalent (not so sure, don’t quote me). I haven’t seen a system yet that doesn’t have &lt;code&gt;cron&lt;/code&gt;. But of course this is an option if you prefer &lt;code&gt;systemd&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So still in that same target server, run &lt;code&gt;crontab -e&lt;/code&gt;. A editor should pop up. In the last line, create the following entry:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;42&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ~/update-ssh.sh&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ~/update-ssh.sh.log&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Change 42 to a random number between 0 and 59, to help lessen load on the server.&lt;/p&gt;
&lt;h3 id=&quot;wait-why-dont-we-just-refresh-every-minute&quot;&gt;Wait, why don’t we just refresh every minute?&lt;/h3&gt;
&lt;p&gt;The reason I set the &lt;code&gt;crontab&lt;/code&gt; here to refresh every hour is because I don’t want to put unnecessary strain on the server or GitHub’s servers. We don’t usually update SSH keys all that often, but we want to strike a balance, because if the interval is too long then we might as well just remote in with our password and change the keys ourselves. An hour is a reasonable interval that won’t put too much strain on the servers involved. You can set it to a shorter interval, say 5 minutes, if you change SSH keys frequently, but do keep in mind that you may be rate-limited.&lt;/p&gt;
&lt;p&gt;The log pipe is not strictly necessary, but it’s good to have in case things go wrong.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;crontab&lt;/code&gt; entry means that your SSH keys will be updated each hour at the random minute you specified.&lt;/p&gt;
&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;The next time you update your SSH keys, update them on GitHub, wait until an hour has elapsed, then remote in to your usual server!&lt;/p&gt;
&lt;p&gt;Thanks for reading! I hope this blog post helped you out.&lt;/p&gt;</content:encoded></item><item><title>MSI laptop USB-C charger design problem</title><link>https://ericswpark.com/blog/2021/2021-01-11-msi-laptop-usb-c-charger-design-problem/</link><guid isPermaLink="true">https://ericswpark.com/blog/2021/2021-01-11-msi-laptop-usb-c-charger-design-problem/</guid><pubDate>Mon, 11 Jan 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you have a recent MSI laptop with a type-C port, this might be relevant to you.&lt;/p&gt;
&lt;p&gt;I discovered that my Creator 15M laptop wouldn’t connect to some USB-C devices. Specifically, my USB-C hub and my phone would never get recognized by the port, but my external SSD (Samsung’s T5) would mount properly.&lt;/p&gt;
&lt;p&gt;Weirdly, the devices that would not get recognized did receive power. The USB-C hub showed the indicator light for power, and my Note 20 Ultra showed a “Check your charger connection” warning, but no “Would you like to allow this computer to access your data” prompt.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;note-20-ultra-charging-message&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;270&quot; height=&quot;579&quot; src=&quot;/_astro/note-20-ultra-charging-message.CH7AXvMn_1zAmVa.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;I first thought this was some driver issue, and spent a couple of hours diagnosing with Device Manager, deleting USB controller drivers, re-installing the Nvidia driver (because the Device Manager mentioned something about Nvidia’s type-C policy controller), and so on and so forth. But then I realized it couldn’t be a driver issue because my phone connected fine with a type-C to USB-A cable. So it was a USB-C problem.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;device-manager&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;992&quot; height=&quot;728&quot; src=&quot;/_astro/device-manager.CrvJSYnK_ZeXAK0.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;I tried calling into MSI’s support line, but the agent on the other end was extremely unenthusiastic. His response was (paraphrasing): “if the type-C port is outputting power, then it’s out of our hands.” Even though I tried explaining the symptoms, he just said it’s probably a compatibility issue and hung up.&lt;/p&gt;
&lt;p&gt;Out of options, I sent a ticket via the MSI’s support website. The response finally solved the problem.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Turns out, it’s a design failure.&lt;/strong&gt; MSI’s laptops’ USB-C ports do not support Thunderbolt (that I knew), but they also &lt;strong&gt;do not support power delivery or display output.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;So because my type-C hub had a USB-C port for charging the device it was connected to, and a HDMI port for display out, it would not work with the type-C port on the laptop. Similarly, my Note 20 Ultra would not connect with the laptop because it supported USB-PD and the laptop didn’t.&lt;/p&gt;
&lt;p&gt;I’m not sure how much of it is true, but it does explain the weird issue I’m having. It would also explain why the external SSD gets recognized, since it doesn’t need to support USB-PD. It’s weird that connecting a device that supports USB-PD to a port that doesn’t support power delivery would disable the data lines, but that’s what the reply from MSI’s support team implies.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;msi-support-team-reply&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1154&quot; height=&quot;661&quot; src=&quot;/_astro/msi-support-team-reply.CDPeMNAY_1et1Uf.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;This really sucks, because the Creator 15M has two type-C ports and two USB-A ports, but the type-C ports are so gimped and unusable they may as well not exist. It’s going to get annoying real fast as more and more devices and accessories support USB-PD. If this is something that they can fix via a software/firmware update that would be great, but as they clearly said it’s a design choice I doubt it’ll ever get fixed.&lt;/p&gt;</content:encoded></item><item><title>Porting Samsung&apos;s Galaxy Note 3 Neo</title><link>https://ericswpark.com/blog/2020/2020-10-11-porting-samsungs-galaxy-note-3-neo/</link><guid isPermaLink="true">https://ericswpark.com/blog/2020/2020-10-11-porting-samsungs-galaxy-note-3-neo/</guid><pubDate>Sun, 11 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;For the past couple of weeks, I’ve been trying to port the Samsung Galaxy Note 3 Neo over to support modern TWRP versions and ROMs. Right now, the device only has a couple of ROM zips and TWRP builds that are all dirty-ported - as in, the images were unpacked and patched by hand. Which means they all run like absolute crap. The TWRP build is old - made back in the KitKat era with a version tag of 2.8.7.0 - and poorly ported, resulting in broken graphics and buttons.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tip #0 - compiling is always better than patchwork porting, because you can get reproducible results every time you build, rather than a hodge-podge of things that may break at any time.&lt;/strong&gt;&lt;/p&gt;
&lt;h1 id=&quot;porting-finding-the-kernel&quot;&gt;&lt;del&gt;Porting&lt;/del&gt; Finding the kernel&lt;/h1&gt;
&lt;p&gt;First off, I spent way too much time thinking I would have to use the GPL kernel dump from Samsung, and tried to figure out exactly what kernel revision Samsung based their kernels off of. Because Samsung only provides you with a tarball and not the &lt;code&gt;git&lt;/code&gt; repository, you need to run a script to brute-force-find the revision. But that takes too long!&lt;/p&gt;
&lt;p&gt;Luckily, @Jackeagle from Team Bliss pointed out &lt;a href=&quot;https://github.com/LineageOS/android_kernel_samsung_msm8974/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;LineageOS already has a kernel for Samsung MSM8974 devices.&lt;/a&gt; Oh, so that’s compatible? Let’s use it!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tip #1 - most SoC kernels (think &lt;code&gt;samsung_msmxxxx&lt;/code&gt; or &lt;code&gt;oneplus_msmxxxx&lt;/code&gt;) are compatible with your device. Someone might have done the hard work of finding the revision for you!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/ericswpark/android_kernel_samsung_msm8974/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;I forked the repository over to my GitHub&lt;/a&gt; and copied the &lt;code&gt;lineage_hltekor_defconfig&lt;/code&gt; file so that I could start with &lt;code&gt;lineage_frescoltekor_defconfig&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So far so good. What’s next?&lt;/p&gt;
&lt;h1 id=&quot;tweaking-the-defconfig&quot;&gt;Tweaking the defconfig&lt;/h1&gt;
&lt;p&gt;Next, I enabled the &lt;code&gt;frescoltekor&lt;/code&gt; defconfigs.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;CONFIG_SEC_FRESCO_PROJECT=y&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;CONFIG_SEC_LOCALE_KOR_FRESCO=y&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;CONFIG_MACH_FRESCOLTEKTT=y&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you do this, remember to &lt;strong&gt;turn off&lt;/strong&gt; the &lt;code&gt;hltekor&lt;/code&gt; defconfigs (or whatever device you’re basing your defconfig off of).&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# CONFIG_SEC_H_PROJECT is not set&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# CONFIG_SEC_LOCALE_KOR_H is not set&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# CONFIG_MACH_HLTEKTT is not set&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Tip #2 - On Samsung devices at least, there are accompanying &lt;code&gt;X_PROJECT&lt;/code&gt; flags that must be enabled/disabled along with your device flags.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Then I tried a build. The build failed. That’s OK. What did I fail on? I2C issues. @pivcer from Team Bliss told me I was missing the proper NFC I2C flags. So I enabled them (while disabling the &lt;code&gt;hltekor&lt;/code&gt; flags):&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# CONFIG_BCM2079X_NFC_I2C is not set&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;CONFIG_SEC_NFC_I2C=y&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# CONFIG_SEC_NFC is not set&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;CONFIG_NFC_PN547=y&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Tip #3 - read the error messages! Often, it will tell you what variable is missing or what is double-defined. Then you have to &lt;code&gt;grep -r &quot;variable_name&quot; .&lt;/code&gt; in the source and find what module the code is in, then figure out what flags you need to enable/disable in the defconfig to get it to properly build.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Then I tried rebuilding. The build failed. OK, what’s next?&lt;/p&gt;
&lt;p&gt;Weirdly, I ran into a situation where the build system wouldn’t find a defined variable:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;CC&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      init/version.o&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  LD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      init/built-in.o&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  LD&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      .tmp_vmlinux1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;/home/users/ericswpark/arm-linux-androideabi-4.8/bin/arm-linux-androideabi-ld:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; warning:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; unwinding&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; may&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; work&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; because&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; input&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 6&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; arch/arm/crypto/built-in.o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; is&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; output&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;/home/users/ericswpark/arm-linux-androideabi-4.8/bin/arm-linux-androideabi-ld:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; warning:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; unwinding&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; may&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; work&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; because&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; input&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 28&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; arch/arm/mach-msm/built-in.o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; is&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; output&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;/home/users/ericswpark/arm-linux-androideabi-4.8/bin/arm-linux-androideabi-ld:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; warning:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; unwinding&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; may&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; work&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; because&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; input&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 55&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; mm/built-in.o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; is&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; output&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;/home/users/ericswpark/arm-linux-androideabi-4.8/bin/arm-linux-androideabi-ld:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; warning:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; unwinding&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; may&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; work&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; because&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; input&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 40&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; fs/built-in.o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; is&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; output&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;/home/users/ericswpark/arm-linux-androideabi-4.8/bin/arm-linux-androideabi-ld:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; warning:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; unwinding&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; may&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; work&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; because&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; input&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 15&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; crypto/built-in.o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; is&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; output&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;/home/users/ericswpark/arm-linux-androideabi-4.8/bin/arm-linux-androideabi-ld:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; warning:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; unwinding&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; may&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; work&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; because&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; input&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 36&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; block/built-in.o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; is&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; output&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;/home/users/ericswpark/arm-linux-androideabi-4.8/bin/arm-linux-androideabi-ld:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; warning:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; unwinding&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; may&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; work&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; because&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; input&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 22&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; lib/built-in.o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; is&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; output&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;/home/users/ericswpark/arm-linux-androideabi-4.8/bin/arm-linux-androideabi-ld:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; warning:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; unwinding&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; may&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; work&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; because&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; input&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 24&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; drivers/built-in.o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; is&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; output&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;/home/users/ericswpark/arm-linux-androideabi-4.8/bin/arm-linux-androideabi-ld:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; warning:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; unwinding&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; may&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; work&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; because&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; input&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 6&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; sound/built-in.o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; is&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; output&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;/home/users/ericswpark/arm-linux-androideabi-4.8/bin/arm-linux-androideabi-ld:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; warning:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; unwinding&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; may&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; work&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; because&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; input&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 6&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; arch/arm/oprofile/built-in.o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; is&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; output&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;/home/users/ericswpark/arm-linux-androideabi-4.8/bin/arm-linux-androideabi-ld:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; warning:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; unwinding&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; may&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; work&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; because&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; input&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 37&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; net/built-in.o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; is&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIDX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; output&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; section&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;drivers/built-in.o:leds-max77803.c:function&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; max77803_led_get_en_value.part.0:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; error:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; undefined&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; reference&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; to&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;led_flash_en&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;drivers/built-in.o:leds-max77803.c:function&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; max77803_led_get_en_value.part.0:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; error:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; undefined&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; reference&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; to&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;led_torch_en&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;drivers/built-in.o:leds-max77803.c:function&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; max77803_led_probe:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; error:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; undefined&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; reference&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; to&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;led_torch_en&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;drivers/built-in.o:leds-max77803.c:function&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; max77803_led_probe:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; error:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; undefined&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; reference&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; to&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;led_flash_en&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;drivers/built-in.o:leds-max77803.c:function&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; max77803_led_probe:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; error:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; undefined&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; reference&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; to&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;camera_class&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;drivers/built-in.o:temphumidity_shtc1.c:function&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; max77803_led_en:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; error:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; undefined&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; reference&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; to&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;led_flash_en&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;drivers/built-in.o:temphumidity_shtc1.c:function&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; max77803_led_en:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; error:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; undefined&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; reference&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; to&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;led_torch_en&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;drivers/built-in.o:leds-max77803.c:function&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; max77803_led_remove:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; error:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; undefined&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; reference&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; to&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;camera_class&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Makefile:889:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; recipe&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; for&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; target&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;.tmp_vmlinux1&apos;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; failed&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;make:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; ***&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [.tmp_vmlinux1] Error 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ericswpark@buildbox:~/android_kernel_samsung_msm8974$&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I spent a long time debugging this, but ultimately whittled it down to one module that wasn’t being built. This module was the Qualcomm MSM camera module, and it wasn’t being built because the configuration file left out my device flag. &lt;a href=&quot;https://github.com/ericswpark/android_kernel_samsung_msm8974/commit/c07074108716e060cbe3c8fae50d85fea7015690&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Adding the device flag solved the issue.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tip #4 - Sometimes, your kernel tree may ship broken. Then it is up to you to make patches like the above. Don’t assume the kernel will work because it’s a GPL dump direct from the manufacturer or because it’s heavily used by a ROM team!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;So with those patches, the build succeeded and I got a &lt;code&gt;zImage&lt;/code&gt; file. Yay! What’s next?&lt;/p&gt;
&lt;h1 id=&quot;porting-twrp&quot;&gt;Porting TWRP&lt;/h1&gt;
&lt;p&gt;I followed much of the same procedure for TWRP. I forked the &lt;code&gt;hltekor&lt;/code&gt; device tree and based my modifications off of that. I copied the prebuilt kernel that I built earlier and tried a build.&lt;/p&gt;
&lt;p&gt;If you try this yourself, you will probably get errors. That’s OK. Read what the errors are on about and make tweaks to your makefiles.&lt;/p&gt;
&lt;p&gt;Yes, this is a very boring process. It took me weeks because I was getting so disappointed at the constant build failures. I really thought I would never finish.&lt;/p&gt;
&lt;p&gt;When I finally got TWRP to compile fully it was really exciting, although not as exciting as when I fixed that stupid camera module bug detailed above in the kernel.&lt;/p&gt;
&lt;p&gt;So what’s next?&lt;/p&gt;
&lt;h1 id=&quot;fin-for-now&quot;&gt;Fin (for now)&lt;/h1&gt;
&lt;p&gt;Unfortunately, this is where my attempt draws to an end. Once I tried flashing the images, the phone wouldn’t boot with the new custom kernel I built.&lt;/p&gt;
&lt;p&gt;My wild guess is some of the &lt;code&gt;hltekor&lt;/code&gt; touchscreen drivers are conflicting with the Note 3 Neo hardware (the Note 3 Neo has a 720p panel while the Note 3 has the 1080p panel, so maybe they have different touchscreen chips and panel configurations).&lt;/p&gt;
&lt;p&gt;To help debug, I tried pulling &lt;code&gt;/proc/last_kmesg&lt;/code&gt;, but it didn’t provide any useful information. I’m guessing the kernel isn’t even getting loaded properly, which is pretty weird because even if it’s a driver issue it should still get me something. I’ve looked around for a debug cable I can use to possibly get serial access, but because I don’t have access to my soldering kit right now I guess it will have to wait.&lt;/p&gt;
&lt;p&gt;So this is as far as I will get for now. If you want to try this process yourself, all of the device and kernel trees are available on my GitHub. Please do let me know via email if you find a fix!&lt;/p&gt;
&lt;p&gt;Thanks to the MSM8974 kernel maintainers and the &lt;code&gt;hltekor&lt;/code&gt; maintainers over at LineageOS for the base tree, and thanks to the members over at the Linux Kernel chat over at Telegram and the team members at Team Bliss for helping me with the kernel compilation.&lt;/p&gt;</content:encoded></item><item><title>Android kernel: use the proper toolchain</title><link>https://ericswpark.com/blog/2020/2020-10-05-android-kernel-use-the-proper-toolchain/</link><guid isPermaLink="true">https://ericswpark.com/blog/2020/2020-10-05-android-kernel-use-the-proper-toolchain/</guid><pubDate>Mon, 05 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;While trying to build an Android kernel from source, I ran into the following error:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  LD      drivers/net/built-in.o&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  LD      drivers/usb/gadget/g_android.o&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  LD      drivers/usb/gadget/built-in.o&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  LD      drivers/usb/built-in.o&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  LD      drivers/built-in.o&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  LD      vmlinux.o&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  MODPOST vmlinux.o&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ERROR: modpost: Found 13 section mismatch(es).&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;To see full details build your kernel with:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&apos;make CONFIG_DEBUG_SECTION_MISMATCH=y&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;To build the kernel despite the mismatches, build with:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&apos;make CONFIG_NO_ERROR_ON_MISMATCH=y&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;(NOTE: This is not recommended)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;/home/users/ideaman924/android_kernel_samsung_msm8974/scripts/Makefile.modpost:98: recipe for target &apos;vmlinux.o&apos; failed&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;make[1]: *** [vmlinux.o] Error 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Makefile:947: recipe for target &apos;vmlinux.o&apos; failed&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;make: *** [vmlinux.o] Error 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ideaman924@build:~/android_kernel_samsung_msm8974$&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Strangely, using different &lt;code&gt;defconfig&lt;/code&gt;s or other kernel sources all led me to this weird &lt;code&gt;modpost&lt;/code&gt; section mismatch error. I researched the issue and found some Stack Overflow posts, none of which were very helpful. Here are some of the posts I read:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/27698976/how-to-fix-section-mismatch-errors-during-cross-compile-of-android&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;How to fix section mismatch errors during cross compile of android&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/6807766/linux-kernel-config-debug-section-mismatch-make-errors&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Linux kernel CONFIG_DEBUG_SECTION_MISMATCH make errors&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/8563978/what-is-kernel-section-mismatch&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;What is kernel section mismatch?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But this was a well-established kernel source straight from LineageOS. I thought code being broken straight from LineageOS’s repositories was unlikely, so I looked around some more for another solution.&lt;/p&gt;
&lt;p&gt;Turns out, it wasn’t a code problem, but rather a toolchain problem. @mochi_wwww from the Linux kernel chat on Telegram figured out that I was using the incorrect toolchain - I had been using &lt;a href=&quot;https://android.googlesource.com/platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;a completely outdated toolchain from several years ago.&lt;/a&gt; So embarrassing.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://forum.xda-developers.com/android/software-hacking/reference-how-to-compile-android-kernel-t3627297&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Now, to my defense, the reference Android kernel building guide&lt;/a&gt; links to that toolchain. I just didn’t know that it was outdated. &lt;a href=&quot;https://android.googlesource.com/platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.8/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Using the proper toolchain magically solved the problem.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So now you know - if you ever run into these weird errors above, try using the correct toolchain for your kernel. It is usually shown in &lt;code&gt;AndroidKernel.mk&lt;/code&gt; if you are using kernel sources from LineageOS. Don’t be an idiot like me and spend hours debugging a code problem that isn’t really a code problem!&lt;/p&gt;</content:encoded></item><item><title>Securely wiping Android devices</title><link>https://ericswpark.com/blog/2020/2020-09-28-securely-wiping-android-devices/</link><guid isPermaLink="true">https://ericswpark.com/blog/2020/2020-09-28-securely-wiping-android-devices/</guid><pubDate>Mon, 28 Sep 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;This post was last updated on January 26th, 2021. (Added information about the awipe project.)&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;When you sell your old Android devices, you want to make sure that your personal information is unrecoverable. On devices with proper flash firmware, the data cells are wiped with a delete command when you wipe the device from recovery. But how do you know that the firmware is doing what it’s supposed to be doing?&lt;/p&gt;
&lt;p&gt;I searched for ways to securely wipe Android devices, but all the answers conflicted with each other. Some people said a factory reset through recovery was enough, while others said an encryption process was required before factory resetting, as the encryption process encrypts free space, leaving no data to be recovered. And some answers were more bleak, suggesting readers to just physically destroy the device if the data in question was extremely sensitive.&lt;/p&gt;
&lt;p&gt;So, ordered in increasing levels of paranoia, here are all the ways of securely wiping your Android device.&lt;/p&gt;
&lt;h1 id=&quot;method-1---just-wipe&quot;&gt;Method 1 - Just wipe&lt;/h1&gt;
&lt;p&gt;If you have a recent Android device that is encrypted straight from the factory, just wipe!&lt;/p&gt;
&lt;p&gt;If your device shipped with Android 10, then it must support &lt;a href=&quot;https://source.android.com/security/encryption/file-based&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;FBE (file-based encryption)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To see if your device is encrypted with FBE, check for the following props: &lt;code&gt;ro.crypto.state&lt;/code&gt; and &lt;code&gt;ro.crypto.type&lt;/code&gt; set to &lt;code&gt;file&lt;/code&gt;.&lt;/p&gt;
&lt;h1 id=&quot;method-2---encrypt-and-wipe&quot;&gt;Method 2 - Encrypt and wipe&lt;/h1&gt;
&lt;p&gt;If you have a semi-recent Android device that supports &lt;a href=&quot;https://source.android.com/security/encryption/full-disk&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;FDE (full-disk encryption)&lt;/a&gt; then encrypt and wipe.&lt;/p&gt;
&lt;p&gt;To see if your device is encrypted with FDE, check for the following props: &lt;code&gt;ro.crypt.state&lt;/code&gt;.&lt;/p&gt;
&lt;h1 id=&quot;method-3a---dd&quot;&gt;Method 3a - dd&lt;/h1&gt;
&lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://www.reddit.com/r/Android/comments/1v568y/for_those_who_want_to_securely_wipe_their_android/cep413z?utm_source=share&amp;#x26;utm_medium=web2x&amp;#x26;context=3&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Original from this Reddit comment&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Reading this comment I remembered most Android devices ship with a rudimentary version of Busybox, which includes the &lt;code&gt;dd&lt;/code&gt; utility.&lt;/p&gt;
&lt;p&gt;Thus, to quickly fill the device with pseudorandom data, one would simply run:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;adb shell&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cat /dev/urandom &gt; random_file&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or, if you’re just as paranoid as the above Redditor, you can run multiple passes:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for i in 0 1 2 3 4 5 6 7 8 9; do cat /dev/urandom &gt; $i; rm $i; done&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Personally, I don’t really see the benefits of running this multiple times, given that flash memory can wear out, and I haven’t seen any articles about data being recovered from wear-leveling cells.&lt;/p&gt;
&lt;h1 id=&quot;method-3b---app&quot;&gt;Method 3b - app&lt;/h1&gt;
&lt;p&gt;I had some free time and decided to turn method 3 into an app that users can run to overwrite free storage space.&lt;/p&gt;
&lt;p&gt;There are a lot of apps on the Play Store that claim to do the same thing - but I can’t really trust them because they are proprietary. So I wrote an app myself that does mostly the same thing.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/ericswpark/awipe/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;It’s called awipe&lt;/a&gt;, and it works by randomly generating data and writing it to a file on the internal storage. So functionally, it is the same as the dd command above.&lt;/p&gt;
&lt;p&gt;The app is better than the other solutions for the following reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You don’t need to enable USB debugging&lt;/li&gt;
&lt;li&gt;You don’t need a computer around&lt;/li&gt;
&lt;li&gt;The app can be sideloaded via a OTG adapter and a USB drive. Unlike Play Store apps, awipe does not require you to sign in with a Google account, as you can sideload it. Once it’s done overwriting free space, just wipe the entire device and no PII will remain.&lt;/li&gt;
&lt;li&gt;The app shows you the progress, unlike &lt;code&gt;dd&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The app is open source&lt;/li&gt;
&lt;li&gt;You don’t need to memorize that &lt;code&gt;dd&lt;/code&gt; command&lt;/li&gt;
&lt;li&gt;Everything happens on-device&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;method-4---physical&quot;&gt;Method 4 - Physical&lt;/h1&gt;
&lt;p&gt;If you are really, really paranoid and you want to remove any possibility of data recovery, then physically destroying the device would be an option.&lt;/p&gt;
&lt;p&gt;If you know how to open up the device and would like to recycle the other components of your device, then grab a drill and make a hole in the flash chip. Refer to iFixit or teardown videos to see where those chips are located. Alternatively, remove the battery and burn the device.&lt;/p&gt;</content:encoded></item><item><title>Repasting my MacBook Pro</title><link>https://ericswpark.com/blog/2020/2020-05-18-repasting-my-macbook-pro/</link><guid isPermaLink="true">https://ericswpark.com/blog/2020/2020-05-18-repasting-my-macbook-pro/</guid><pubDate>Mon, 18 May 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This is going to be a short article, because I forgot to record anything or take photos. I was going to initially disassemble the entire thing, then reassemble it, then start recording again as I went through the process. In reality, I only disassembled it once and vowed myself to never do it again because it is &lt;strong&gt;that&lt;/strong&gt; complicated.&lt;/p&gt;
&lt;p&gt;There are at least 4 different screw types, not to mention the countless ribbon cables scattered here and there that make disassembly a complete nightmare. Also, Apple had the smartest idea of putting the retaining screws of the heatsink on the other side of the logic board, which means re-pasting your laptop requires you to disassemble the entire thing.&lt;/p&gt;
&lt;p&gt;So, this is the only photo I managed to take (enjoy!):&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;macbook-pro-internals&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1024&quot; height=&quot;768&quot; src=&quot;/_astro/macbook-pro-internals.BdgNt3Kl_Z2frflT.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;Gotta love that overboard Apple engineering 😀&lt;/p&gt;
&lt;p&gt;But I think the trouble was worth it in the end. The stock thermal paste was all dried up and nasty, resulting in the laptop idling at around 49 degrees Celsius. Now, it idles at around 40-44 degrees Celsius, which means I scored about 5 degrees Celsius out of the entire ordeal. If you want that improvement, then yeah, sure. (Just remember, I don’t assume any responsibility for your actions.)&lt;/p&gt;
&lt;p&gt;And by the way, here is a screw organizer:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;screw-organizer&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;2480&quot; height=&quot;3504&quot; src=&quot;/_astro/screw-organizer.DsRxjgB-_ZS8yOY.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;You’ll need it if you’re attempting this crazy thing. Pay attention to all the different screw types. P5 is Pentalobe, and the various types with the T- suffix are the Torx types.&lt;/p&gt;
&lt;p&gt;Remember to stick the screws &lt;strong&gt;exactly&lt;/strong&gt; where it indicates on the paper, as you’ll find different length-ed screws literally everywhere, particularly in step 1, step 6, step 7, and step 10. I used Blu-tack so that the screws wouldn’t fall off and disappear into the fourth dimension, as all screws tend to do that when I ever do a disassembly project.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Be extremely careful with the ribbon cables&lt;/strong&gt;. They are very fragile and I almost tore some of them. &lt;strong&gt;Go extremely slowly&lt;/strong&gt;, and don’t use any force whatsoever.&lt;/p&gt;
&lt;p&gt;Good luck!&lt;/p&gt;</content:encoded></item><item><title>Backup your WordPress site (manually)</title><link>https://ericswpark.com/blog/2020/2020-04-04-backup-your-wordpress-site-manually/</link><guid isPermaLink="true">https://ericswpark.com/blog/2020/2020-04-04-backup-your-wordpress-site-manually/</guid><pubDate>Sat, 04 Apr 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;TL;DR – don’t use automated solutions. Do it properly and do it manually!&lt;/p&gt;
&lt;p&gt;Create a temporary directory to work in:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mkdir&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; backup-20200105&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; backup-20200105&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Time to dump the MySQL database! It’s not that hard.&lt;/p&gt;
&lt;p&gt;Quick note before you run this – this is assuming your MySQL installation authenticates via UNIX sockets. If you have password authentication I suggest moving away from it if your MySQL database is on the same server as your WordPress site. Less chance of things getting messed up and you don’t have to remember yet another root password that could be leaked.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; mysqldump&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --add-drop-table&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -u&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; root&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; wordpress&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; database.sql&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If it prompts you for your sudo password, supply it. If it prompts you for your MySQL password, then you’ve done something wrong.&lt;/p&gt;
&lt;p&gt;Now you need to copy the static files and what not. Run:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;cp&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -a&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /var/www/example.com&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ./&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;-a&lt;/code&gt; flag tells the &lt;code&gt;cp&lt;/code&gt; program to copy over all attributes. It doesn’t always work, however. In my case it rewrote the &lt;code&gt;www-data&lt;/code&gt; user to &lt;code&gt;ericswpark&lt;/code&gt;. Nothing a simple &lt;code&gt;chown&lt;/code&gt; can fix, though!&lt;/p&gt;
&lt;p&gt;Time to tarball the entire thing. Back out and then run:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;tar&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -czvf&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; backup-20200105.tar.gz&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; backup-20200105/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You’ll get a neat tarball you can throw on Google Drive or something like that! Wasn’t that easy?&lt;/p&gt;
&lt;h1 id=&quot;restoration&quot;&gt;Restoration&lt;/h1&gt;
&lt;p&gt;Get the tarball and unpack it:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;tar&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -xzvf&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; backup-20200105.tar.gz&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Go inside, and then run:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; mysql&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -u&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; root&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; wordpress&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; database.sql&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;cp&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -a&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; example.com&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /var/www/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Restore permissions if required. Done!&lt;/p&gt;</content:encoded></item><item><title>GPG - The Complete Crash Course</title><link>https://ericswpark.com/blog/2020/2020-02-25-gpg-the-complete-crash-course/</link><guid isPermaLink="true">https://ericswpark.com/blog/2020/2020-02-25-gpg-the-complete-crash-course/</guid><pubDate>Tue, 25 Feb 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Let’s jump right in with:&lt;/p&gt;
&lt;h1 id=&quot;creating-a-key&quot;&gt;Creating a key&lt;/h1&gt;
&lt;p&gt;To create a key, run:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --gen-key&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If that command does not guide you through the interactive process: selecting key size, key type, and what not — then run:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --full-generate-key&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should see the following:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gpg (GnuPG) 2.2.17; Copyright (C) 2019 Free Software Foundation, Inc.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;This is free software: you are free to change and redistribute it.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;There is NO WARRANTY, to the extent permitted by law.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gpg: directory &apos;/Users/ericswpark/.gnupg&apos; created&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gpg: keybox &apos;/Users/ericswpark/.gnupg/pubring.kbx&apos; created&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Please select what kind of key you want:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    (1) RSA and RSA (default)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    (2) DSA and Elgamal&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    (3) DSA (sign only)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    (4) RSA (sign only)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Your selection?&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Press Enter. RSA and RSA is fine.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;RSA keys may be between 1024 and 4096 bits long.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;What keysize do you want? (2048)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For keysize, I recommend 4096 bits. Type &lt;code&gt;4096&lt;/code&gt; and press Enter.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Please specify how long the key should be valid.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        0 = key does not expire&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;n&gt;  = key expires in n days&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;n&gt;w = key expires in n weeks&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;n&gt;m = key expires in n months&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;n&gt;y = key expires in n years&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Key is valid for? (0)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, the best practice is to set a expiration date for your key. But GnuPG also has another feature else in case you don’t want to repeat this key generation process every couple of years: creating subkeys, where you just create more and more keys with your “master” key. We’ll go more into subkeys below, but for now you can just leave it to never expire. Press Enter.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Key does not expire at all&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Is this correct? (y/N)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;GnuPG warns us that the key won’t expire. Type &lt;code&gt;y&lt;/code&gt;, and then Enter.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;GnuPG needs to construct a user ID to identify your key.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Real name:&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This part should be easy. Just fill out your information, like so:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Real name: Eric Park&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Email address: me@ericswpark.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Comment: https://ericswpark.com/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;You selected this USER-ID:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;Eric Park (https://ericswpark.com/) &amp;#x3C;me@ericswpark.com&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit?&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Press &lt;code&gt;o&lt;/code&gt;, and then Enter. (If you want to change anything, just type the letter in parentheses for the field you want to change, and then press Enter!)&lt;/p&gt;
&lt;p&gt;GPG should now prompt you for the passphrase. Type it in, then press Enter. You will need to do this twice. This will be the safeguard against anyone breaching your computer and taking the key. They won’t be able to use the key without the passphrase. (This does NOT mean, however, that you should not revoke your key. If you notice your key has been compromised, &lt;a href=&quot;#revoking-the-key&quot;&gt;revoke the key immediately.&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Then GPG should start the process of generating your public/private key pair. This warning should come up:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;We need to generate a lot of random bytes. It is a good idea to perform&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;some other action (type on the keyboard, move the mouse, utilize the&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;disks) during the prime generation; this gives the random number&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;generator a better chance to gain enough entropy.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If your computer is fairly modern and fast, this process should only take 30 seconds or so. Just shake your mouse pointer around until the terminal prompt changes to this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gpg: /Users/ericswpark/.gnupg/trustdb.gpg: trustdb created&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gpg: key 56F399E7A57D6E5D marked as ultimately trusted&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gpg: directory &apos;/Users/ericswpark/.gnupg/openpgp-revocs.d&apos; created&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gpg: revocation certificate stored as &apos;/Users/ericswpark/.gnupg/openpgp-revocs.d/475C562EBC0520048AE79ADD56F399E7A57D6E5D.rev&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;public and secret key created and signed.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;pub   rsa4096 2019-12-20 [SC]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    475C562EBC0520048AE79ADD56F399E7A57D6E5D&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;uid                      Eric Park (https://ericswpark.com/) &amp;#x3C;me@ericswpark.com&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;sub   rsa4096 2019-12-20 [E]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Congratulations! You created your first GPG key.&lt;/p&gt;
&lt;h1 id=&quot;listing-available-keys&quot;&gt;Listing available keys&lt;/h1&gt;
&lt;p&gt;To see your available keys, run:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --list-keys&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This should print something like:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gpg: checking the trustdb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gpg: marginals needed: 3  completes needed: 1  trust model: pgp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;/Users/ericswpark/.gnupg/pubring.kbx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;------------------------------------&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;pub   rsa4096/56F399E7A57D6E5D 2019-12-20 [SC]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    475C562EBC0520048AE79ADD56F399E7A57D6E5D&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;uid                 [ultimate] Eric Park (https://ericswpark.com/) &amp;#x3C;me@ericswpark.com&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;sub   rsa4096/2698CAAB8C3C9C8C 2019-12-20 [E]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that some distributions and operating systems may not show the key ID in full. To see the correct output, run this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --list-keys&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --keyid-format&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; long&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also use &lt;code&gt;short&lt;/code&gt;, if you have a small number of keys and don’t want to type out the entire UUID!&lt;/p&gt;
&lt;h1 id=&quot;publishing-your-key&quot;&gt;Publishing your key&lt;/h1&gt;
&lt;p&gt;To give your key to others, you must first publish your key.&lt;/p&gt;
&lt;p&gt;First, you need to find your public key ID. To do this, &lt;a href=&quot;#listing-available-keys&quot;&gt;run the command from “Listing available keys.”&lt;/a&gt; In the output, find this line:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;pub   rsa4096/56F399E7A57D6E5D 2019-12-20 [SC]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Anything after &lt;code&gt;rsa4096/&lt;/code&gt; should be different for you. That, &lt;code&gt;56F399E7A57D6E5D&lt;/code&gt;, is your public key ID.&lt;/p&gt;
&lt;p&gt;Then send it off to the keyserver!&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --send-keys&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; YOUR_PUBLIC_KEY_ID_HERE&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Following the syntax above, I would run:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --send-keys&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 56F399E7A57D6E5D&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;receiving-published-keys&quot;&gt;Receiving published keys&lt;/h1&gt;
&lt;p&gt;If you &lt;a href=&quot;#publishing-your-key&quot;&gt;followed the steps above to publish your public keys&lt;/a&gt;, then it should be simple for others to retrieve your keys and start using them to encrypt data for your eyes or verify something is actually made by you.&lt;/p&gt;
&lt;p&gt;Publish your key ID somewhere, maybe in a blog (&lt;a href=&quot;https://ericswpark.com/.well-known/gpg.txt&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;hint, hint&lt;/a&gt;). Then others can just download your key using this command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --recv-keys&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; PUBLIC_KEY_ID_HERE&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So for people to get my key ID (note that this is NOT my actual key, it’s just for this tutorial, look at &lt;a href=&quot;https://ericswpark.com/.well-known/gpg.txt&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;my GPG page&lt;/a&gt; for the actual public key), they can just run:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --recv-keys&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 56F399E7A57D6E5D&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If GPG throws an error, it could be that the key is not available. Search for the key:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --search-keys&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; PUBLIC_KEY_ID_HERE&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --search-keys&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 56F399E7A57D6E5D&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;using-custom-keyservers&quot;&gt;Using custom keyservers&lt;/h1&gt;
&lt;p&gt;If you just run &lt;code&gt;gpg --send-keys&lt;/code&gt; or &lt;code&gt;gpg --recv-keys&lt;/code&gt; without any other parameters, GnuPG usually uses the default keyserver, which is at &lt;code&gt;hkps://hkps.pool.sks-keyservers.net&lt;/code&gt;. You can see that this URL is a bit unique: rather than &lt;code&gt;https&lt;/code&gt;, it now reads &lt;code&gt;hkps&lt;/code&gt;. &lt;code&gt;hkp&lt;/code&gt; and &lt;code&gt;hkps&lt;/code&gt; is what GnuPG uses for the keyserver locations.&lt;/p&gt;
&lt;p&gt;For example, when I had to verify my Manjaro &lt;code&gt;.iso&lt;/code&gt;, I had to download the keyfiles of Philip Müller, the project leader, from a different keyserver. Their wiki gives the address of: &lt;code&gt;hkp://pool.sks-keyservers.net&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;So to use a custom keyserver, just specify it as a parameter:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --keyserver&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; hkps://keyserver.url.here&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;parameters&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; you&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; want&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; to&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ru&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;n&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, to fetch Philip Müller’s key, I had to run:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --keyserver&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; hkp://pool.sks-keyservers.net&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --recv-keys&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 11C7F07E&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that the &lt;code&gt;--keyserver&lt;/code&gt; parameter must be typed before the other parameters.&lt;/p&gt;
&lt;p&gt;As of 2019, the default SKS keyserver that GPG uses seems to be dead. &lt;a href=&quot;https://code.firstlook.media/the-death-of-sks-pgp-keyservers-and-how-first-look-media-is-handling-it&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Read this article from First Look Media to learn more.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;They recommend using the &lt;code&gt;keys.openpgp.org&lt;/code&gt; keyserver to upload keys, which uses the &lt;a href=&quot;https://gitlab.com/hagrid-keyserver/hagrid/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Hagrid&lt;/a&gt; backend for serving keys. Apparently this is better since the SKS backend has a number of vulnerabilities being abused right now.&lt;/p&gt;
&lt;h1 id=&quot;creating-a-revocation-certificate&quot;&gt;Creating a revocation certificate&lt;/h1&gt;
&lt;p&gt;This step is needed for when you accidentally expose your private key. Maybe you accidentally sent the wrong key file when sending your public key to your friend (this is why we use keyservers, like shown above)&lt;/p&gt;
&lt;p&gt;Execute the following command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --gen-revoke&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; PUBLIC_KEY_ID_HERE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; PUBLIC_KEY_ID_HERE_revoke.asc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So for me, it would be:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --gen-revoke&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 56F399E7A57D6E5D&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 56F399E7A57D6E5D_revoke.asc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That command will produce something like the following output:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;sec  rsa4096/56F399E7A57D6E5D 2019-12-20 Eric Park (https://ericswpark.com/) &amp;#x3C;me@ericswpark.com&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Create a revocation certificate for this key? (y/N)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Press &lt;code&gt;y&lt;/code&gt; and then Enter to confirm.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Please select the reason for the revocation:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    0 = No reason specified&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    1 = Key has been compromised&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    2 = Key is superseded&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    3 = Key is no longer used&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    Q = Cancel&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;(Probably you want to select 1 here)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Your decision?&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Press Enter to accept defaults, unless you have a specific reason behind the revocation.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Enter an optional description; end it with an empty line:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This part is optional. Continue by pressing Enter.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Reason for revocation: Key has been compromised&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Is this okay? (y/N)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Press &lt;code&gt;y&lt;/code&gt; and then Enter to confirm.&lt;/p&gt;
&lt;p&gt;GPG will then ask you for your passcode to sign this revocation certificate with your private key. Type in your passcode, and then press Enter.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ASCII armored output forced.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Revocation certificate created.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Please move it to a medium which you can hide away; if Mallory gets&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;access to this certificate he can use it to make your key unusable.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;It is smart to print this certificate and store it away, just in case&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;your media become unreadable.  But have some caution:  The print system of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;your machine might store the data and make it available to others!&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fun fact: Mallory is an alias the GPG community gave for “bad actors”. The more you know! Also, if you are confused about the term “ASCII armored output”, &lt;a href=&quot;#what-is-ascii-armored&quot;&gt;check out this section explaining the differences between regular GPG files and ASCII-armored GPG files.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Finally, save the revocation certificate somewhere safe. This can be used to completely wipe out your key pair and render it useless, as the message warns above. Some paranoid people even print the ASCII-armored output and when it’s time to use them, they utilize OCR software to read the printout! But that’s probably overkill.&lt;/p&gt;
&lt;h1 id=&quot;revoking-the-key&quot;&gt;Revoking the key&lt;/h1&gt;
&lt;p&gt;Get the certificate you generated &lt;a href=&quot;#creating-a-revocation-certificate&quot;&gt;in the section above&lt;/a&gt;. Let’s locally revoke your key first:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --import&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; PUBLIC_KEY_ID_HERE_revoke.asc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So I would run:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --import&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 56F399E7A57D6E5D_revoke.asc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then follow the steps &lt;a href=&quot;#publishing-your-key&quot;&gt;in “Publishing your key”&lt;/a&gt; again to update the public key in the keyserver and let others know your key has been compromised. Note that once the changes have been published, &lt;a href=&quot;#stop-revoking-the-key&quot;&gt;you will not be able to stop the revocation locally.&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&quot;un-revoke-the-key&quot;&gt;Un-revoke the key&lt;/h1&gt;
&lt;p&gt;You can un-revoke your key &lt;strong&gt;only if&lt;/strong&gt; you haven’t pushed the revoked public key to a keyserver.&lt;/p&gt;
&lt;p&gt;Let’s make GPG forget that you requested the revocation:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --expert&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --delete-key&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; PUBLIC_KEY_ID_HERE&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For me it would be:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --expert&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --delete-key&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 56F399E7A57D6E5D&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;--expert&lt;/code&gt; parameter allows you to delete the public key only.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gpg (GnuPG) 2.2.17; Copyright (C) 2019 Free Software Foundation, Inc.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;This is free software: you are free to change and redistribute it.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;There is NO WARRANTY, to the extent permitted by law.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;pub  rsa4096/56F399E7A57D6E5D 2019-12-20 Eric Park (https://ericswpark.com/) &amp;#x3C;me@ericswpark.com&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Delete this key from the keyring? (y/N)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Type &lt;code&gt;y&lt;/code&gt;, and then Enter to confirm.&lt;/p&gt;
&lt;p&gt;Now we need to download the clean public key from the keyserver, that hasn’t been marked as revoked. Follow the steps from &lt;a href=&quot;#receiving-published-keys&quot;&gt;“Receiving published keys” for this process.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Alternatively, you could also &lt;a href=&quot;#importing-keys&quot;&gt;import a local copy of the public key&lt;/a&gt;, if you had a backup available with the key not being marked as revoked.&lt;/p&gt;
&lt;h1 id=&quot;importing-keys&quot;&gt;Importing keys&lt;/h1&gt;
&lt;p&gt;Importing keys is also crucial. You will use it all the time to get keys from other sources (in case you don’t trust keyservers).&lt;/p&gt;
&lt;p&gt;Fortunately, this process is fairly simple. You just need a GPG file, or an &lt;a href=&quot;#what-is-ascii-armored&quot;&gt;ASCII-armored&lt;/a&gt; GPG file. They usually have the following extensions: &lt;code&gt;.gpg&lt;/code&gt;, &lt;code&gt;.asc&lt;/code&gt;, or &lt;code&gt;.key&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Run the following command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --import&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; KEY_FILE_NAME.asc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;what-is-ascii-armored&quot;&gt;What is ASCII-armored?&lt;/h1&gt;
&lt;p&gt;ASCII-armored basically means it’s in text form and not in a binary format. This makes it useful, for example, when you’re sending the file over email.&lt;/p&gt;
&lt;p&gt;Usually, you can force ASCII-armored by appending the parameter &lt;code&gt;--armor&lt;/code&gt; to your command - this will ensure your file is editable via text editor (not that you would ever want to edit something outputted by GPG).&lt;/p&gt;
&lt;h1 id=&quot;signing-keys&quot;&gt;Signing keys&lt;/h1&gt;
&lt;p&gt;This is also part of the key management process. Don’t worry, we’re getting to the fun part of encrypting, decrypting, and certifying data, but this part may actually be more fun to you.&lt;/p&gt;
&lt;p&gt;I first have to explain the &lt;a href=&quot;https://en.wikipedia.org/wiki/Web_of_trust&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;“web of trust.”&lt;/a&gt; Quite honestly, I like this model better than the &lt;a href=&quot;https://en.wikipedia.org/wiki/Certificate_authority&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;“Certificate Authority”&lt;/a&gt; model that SSL uses.&lt;/p&gt;
&lt;p&gt;Do you know how you get the padlock on your browser, next to that URL? That green padlock is letting you know that the site you’re visiting is actually the site you want, and that the contents are encrypted in transit.&lt;/p&gt;
&lt;p&gt;To do this, the site owner must get an SSL certificate from a root authority, such as Let’s Encrypt, Comodo, DigiCert, and so on. Out of these providers, Let’s Encrypt is the only one that does this for free, but the reason they can provide it for free is that they are a) sponsored by a lot of charitable organizations, and b) because the signing window is small (i.e. you can only have it signed for like 3 months or so.) Also, Let’s Encrypt relies on an automated way of verifying websites, saving costs.&lt;/p&gt;
&lt;p&gt;So why are the rest of those providers asking for money for SSL certificates? Well, every time they give out the SSL certificates, the providers need to make sure that they’re giving the certificates to the actual owner of the website. Bad stuff can happen if they accidentally hand out SSL certificates for the wrong owners/servers, &lt;a href=&quot;https://www.theregister.co.uk/2018/02/07/beware_the_coming_chrome_certificate_apocalypse/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;something that has definitely happened in the past&lt;/a&gt;. When this happens frequently, browser developers typically de-list the specific root authority that is causing problems, so the providers have a lot of incentive to verify owners properly.&lt;/p&gt;
&lt;p&gt;Of course, there’s that entire issue of governments spying on their citizens by installing rogue root authorities, but that’s not in the scope of this article. What I’m trying to point out here is that this model is &lt;em&gt;horrible&lt;/em&gt;. It’s horrible because you’re relying on that one central authority to be the “good guys” and not create certificates for people that don’t deserve them.&lt;/p&gt;
&lt;p&gt;To counter this, a different model that GPG uses is the “web of trust”. You’ll see why it’s called a web in a moment. Let’s say I have a friend, A. I know who A is as I’ve met him in person, so I sign A’s key. Later, A meets B, a close friend of A but a complete stranger to me. B asks A if A can sign his key. Because A trusts B, A does the same, and signs B’s key. Now, A suggests to me that I should host a party with B. Since I don’t know B, I need to share contact information with B. I go to B’s website and find B’s public key. Now I know I can trust B, because B is trusted by A, and I trust A.&lt;/p&gt;
&lt;p&gt;This model works wonders because the more keys you sign, the more keys they sign, the higher this level of confidence that the keys belong to the right person. This is why it’s called a web of trust. When put together, the entire system looks like a web:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://upload.wikimedia.org/wikipedia/commons/thumb/4/4e/Web_of_Trust-en.svg/1200px-Web_of_Trust-en.svg.png&quot; alt=&quot;web-of-trust-diagram&quot;&gt;&lt;/p&gt;
&lt;p&gt;&amp;#x3C; From &lt;a href=&quot;https://en.wikipedia.org/wiki/Web_of_trust&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://en.wikipedia.org/wiki/Web_of_trust&lt;/a&gt; &gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Key_signing_party&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;People even host real-life parties to sign keys!&lt;/a&gt; Of course, that doesn’t mean you should just sign any random stranger’s key… You should always verify their identity!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://imgs.xkcd.com/comics/responsible_behavior.png&quot; alt=&quot;xkcd-responsible-behavior&quot;&gt;&lt;/p&gt;
&lt;p&gt;&amp;#x3C; From &lt;a href=&quot;https://xkcd.com/364/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;https://xkcd.com/364/&lt;/a&gt; &gt;&lt;/p&gt;
&lt;p&gt;The process is simple - you note down your public key ID on paper (you usually do not bring your computer containing your key pair since that can increase the chances of your private key being leaked) and attend a key signing party. During the event, you write down as many public key IDs as you can, provided that you have verified each individual’s identity first. Afterwards, you come home, &lt;a href=&quot;#receiving-published-keys&quot;&gt;you receive the keys&lt;/a&gt;, and then you sign them:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --sign-key&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; PUBLIC_KEY_ID_OF_STRANGER&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then you &lt;a href=&quot;#publishing-your-key&quot;&gt;publish them again&lt;/a&gt; to reflect that you trust the key. (Note that you should publish their public key, NOT your public key.) Warning: if their key was not published in the first place, publishing them may be rude, so you should always ask for the key-owner’s permission.&lt;/p&gt;
&lt;p&gt;For example, if I wanted to sign that Manjaro developer’s key, I would type:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --sign-key&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 11C7F07E&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This would result in the following output:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;pub  rsa2048/CAA6A59611C7F07E&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    created: 2012-05-05  expires: never       usage: SC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    trust: unknown       validity: unknown&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;sub  rsa2048/320011450576724A&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    created: 2012-05-05  expires: never       usage: E&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[ unknown] (1). Philip Müller (Called Little) &amp;#x3C;philm@manjaro.org&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;pub  rsa2048/CAA6A59611C7F07E&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    created: 2012-05-05  expires: never       usage: SC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    trust: unknown       validity: unknown&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Primary key fingerprint: E4CD FE50 A2DA 85D5 8C8A  8C70 CAA6 A596 11C7 F07E&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    Philip Müller (Called Little) &amp;#x3C;philm@manjaro.org&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Are you sure that you want to sign this key with your&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;key &quot;Eric Park (https://ericswpark.com/) &amp;#x3C;me@ericswpark.com&gt;&quot; (56F399E7A57D6E5D)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Really sign? (y/N)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Type &lt;code&gt;y&lt;/code&gt;, and then Enter. GPG will ask you for your password. Type it, then press Enter to sign the key.&lt;/p&gt;
&lt;p&gt;Remember that you may also need to &lt;a href=&quot;#what-is-trust&quot;&gt;update their “trust level”&lt;/a&gt;. (Down below)&lt;/p&gt;
&lt;h1 id=&quot;key-signing-party-guide&quot;&gt;Key signing party guide&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Do not bring your computer. This increases the chances of your computer getting infected with key-stealing malware, or you accidentally misplacing your private key. Just publish your public key, jot down your public key ID somewhere, and then bring that to the event.&lt;/li&gt;
&lt;li&gt;For friends, just sign their keys. For strangers (like if you are at an international event or something), verify their identity with something like their passport, and make sure their identification papers are not forged.&lt;/li&gt;
&lt;li&gt;You can always batch-sign keys after the key signing event. Weird, right? Yeah, the actual key signing happens after the event.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;what-is-trust&quot;&gt;What is trust?&lt;/h1&gt;
&lt;p&gt;Trust, in GnuPG, is this mental note you put down in your own keyring, noting down how “trustworthy” an individual is.&lt;/p&gt;
&lt;p&gt;Let’s say I have this great nerd friend that will only sign responsibly by verifying all identities before signing. I’ll give this friend a higher trust value because I believe that this friend will make good decisions.&lt;/p&gt;
&lt;p&gt;Compare that to a key signing event, where I’m signing the keys of complete strangers. I don’t even know if they’re responsible or not - I’ve only just met them! Therefore, when I sign their key, I assign them a trust value of unknown, or 1.&lt;/p&gt;
&lt;p&gt;Note that if you sign someone’s key, their default trust value will be 1 - or unknown. This is usually what you want, unless you really trust the individual to make good decisions.&lt;/p&gt;
&lt;p&gt;To edit someone’s trust value, use this command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --edit-key&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; SOMEONES_PUBLIC_KEY_ID&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then type &lt;code&gt;trust&lt;/code&gt;, and Enter to edit their trust value. Press Enter to save the trust value, and then type &lt;code&gt;quit&lt;/code&gt; to exit the application.&lt;/p&gt;
&lt;h1 id=&quot;backing-up-your-keys&quot;&gt;Backing up your keys&lt;/h1&gt;
&lt;p&gt;If you lose your key pair, horrible things can happen. And these events are all too common. Dead hard drive, lost USB, computer blows up, et cetera. Let’s prevent a catastrophe!&lt;/p&gt;
&lt;p&gt;If you want to be lazy, just back up the entire &lt;code&gt;.gnupg&lt;/code&gt; directory. There you go, keep that safe. But if you want a more thorough approach, run:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --export-secret-keys&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --armor&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; KEY_ID&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; KEY_ID_secret.asc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This should save your secret key to the file &lt;code&gt;KEY_ID_secret.asc&lt;/code&gt;. Back it up somewhere safe.&lt;/p&gt;
&lt;p&gt;For added convenience, also back up your public key. This is not strictly required, but deriving the public key from the private key is annoying:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --export&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --armor&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; KEY_ID&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; KEY_ID_public.asc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that if you ever re-import your keys, &lt;a href=&quot;#what-is-trust&quot;&gt;you may have to set their trust level to 5 again&lt;/a&gt; to let GPG know that the keys are yours.&lt;/p&gt;
&lt;h1 id=&quot;subkeys&quot;&gt;Subkeys!&lt;/h1&gt;
&lt;p&gt;Subkeys are basically smaller keys made from your master key file. You can play a bit loose with them, because even if they get exposed you can easily revoke them with your master key.&lt;/p&gt;
&lt;p&gt;Run the following command to edit your master key:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --edit-key&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; YOUR_KEY_ID&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will result in the following output:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gpg (GnuPG) 2.2.17; Copyright (C) 2019 Free Software Foundation, Inc.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;This is free software: you are free to change and redistribute it.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;There is NO WARRANTY, to the extent permitted by law.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Secret key is available.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;sec  rsa4096/56F399E7A57D6E5D&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    created: 2019-12-20  expires: never       usage: SC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    trust: ultimate      validity: ultimate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ssb  rsa4096/2698CAAB8C3C9C8C&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    created: 2019-12-20  expires: never       usage: E&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[ultimate] (1). Eric Park (https://ericswpark.com/) &amp;#x3C;me@ericswpark.com&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gpg&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s add a key using &lt;code&gt;addkey&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Please select what kind of key you want:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    (3) DSA (sign only)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    (4) RSA (sign only)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    (5) Elgamal (encrypt only)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    (6) RSA (encrypt only)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Your selection?&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You probably need two subkeys: one for signing, and one for encrypting. There’s already a subkey for encrypting, so for now, I’ll show how to create a signing subkey. Type &lt;code&gt;4&lt;/code&gt; and then press Enter:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;RSA keys may be between 1024 and 4096 bits long.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;What keysize do you want? (2048)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Again, we will use &lt;code&gt;4096&lt;/code&gt; here.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Requested keysize is 4096 bits&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Please specify how long the key should be valid.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        0 = key does not expire&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;n&gt;  = key expires in n days&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;n&gt;w = key expires in n weeks&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;n&gt;m = key expires in n months&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;n&gt;y = key expires in n years&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Key is valid for? (0)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Enter again, key does not expire (again, you can just revoke them if you lose them). If you need a temporary key that needs to expire, use the syntax above to set a time limit for them. For example, if you want them to expire after 3 months, then type &lt;code&gt;3m&lt;/code&gt;. But for now, I’ll accept the defaults:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Key does not expire at all&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Is this correct? (y/N)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Press &lt;code&gt;y&lt;/code&gt;, and then Enter.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Really create? (y/N)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;y&lt;/code&gt; again, and then Enter. It should ask you for your passphrase, and then warn you about entropy, and then create that beautiful subkey:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;sec  rsa4096/56F399E7A57D6E5D&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    created: 2019-12-20  expires: never       usage: SC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    trust: ultimate      validity: ultimate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ssb  rsa4096/2698CAAB8C3C9C8C&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    created: 2019-12-20  expires: never       usage: E&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ssb  rsa4096/CFE50DE6144F5CA0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    created: 2019-12-20  expires: never       usage: S&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[ultimate] (1). Eric Park (https://ericswpark.com/) &amp;#x3C;me@ericswpark.com&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gpg&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can see the subkey right there, with the ID &lt;code&gt;CFE50DE6144F5CA0&lt;/code&gt;! Now we need to save the changes. Type &lt;code&gt;save&lt;/code&gt;, and then Enter.&lt;/p&gt;
&lt;p&gt;To export the key and use it somewhere else, type:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --export-secret-subkeys&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --armor&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; CFE50DE6144F5CA0!&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; CFE50DE6144F5CA0_subkey.asc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The exclamation mark is &lt;strong&gt;extremely important&lt;/strong&gt;. Without it, you will be exporting ALL of the subkeys, which might not be what you want (for example, you wouldn’t want to export your decryption subkey to a sensitive computer!)&lt;/p&gt;
&lt;p&gt;Then on the target computer, type:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --import&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; CFE50DE6144F5CA0_subkey.asc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To import the subkey! If you want to revoke it at any time, just edit your master key again, and then type &lt;code&gt;revkey&lt;/code&gt;. Finally, publish your changes to the keyserver.&lt;/p&gt;
&lt;p&gt;To delete your main keyring, run:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --delete-secret-key&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; KEY_ID&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that this also removes your keyring (be careful!)&lt;/p&gt;
&lt;h1 id=&quot;keeping-keys-updated&quot;&gt;Keeping keys updated&lt;/h1&gt;
&lt;p&gt;Keys constantly change. People may sign keys, revoke them, or even generate new ones! So to keep your keyring up-to-date, it’s a good idea to run this periodically:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --refresh-keys&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This also updates your public key, if you’ve published them on a keyserver.&lt;/p&gt;
&lt;p&gt;Now pat yourself on the back if you’ve come this far, as we will now get into how to actually utilize those keys!&lt;/p&gt;
&lt;h1 id=&quot;encrypting-files&quot;&gt;Encrypting files&lt;/h1&gt;
&lt;p&gt;You need the other person’s public key for this. Type the following command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --encrypt&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; file.tar.gz&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;GPG will prompt you to enter the user ID. Enter their name if their name is unique in your keyring, or their public key ID.&lt;/p&gt;
&lt;p&gt;You can also specify it with the parameters:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --encrypt&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; file.tar.gz&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -r&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Eric Park&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Remember, you can &lt;a href=&quot;#what-is-ascii-armored&quot;&gt;ASCII-armor&lt;/a&gt; your GPG file here!&lt;/p&gt;
&lt;h1 id=&quot;decrypting-files&quot;&gt;Decrypting files&lt;/h1&gt;
&lt;p&gt;This one is easy, since your private key file is already there. Run the following command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --decrypt&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; file.tar.gz.gpg&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or &lt;code&gt;.asc&lt;/code&gt;, if the file is &lt;a href=&quot;#what-is-ascii-armored&quot;&gt;ASCII-armored&lt;/a&gt;.&lt;/p&gt;
&lt;h1 id=&quot;signing-files&quot;&gt;Signing files&lt;/h1&gt;
&lt;p&gt;Depending on how you want to sign a file, there are a couple of options. If you want to make a compressed, signed file, run the following command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --sign&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; file.tar.gz&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This command will create either &lt;code&gt;file.tar.gz.gpg&lt;/code&gt; or &lt;code&gt;file.tar.gz.asc&lt;/code&gt;, depending on whether or not you supplied the &lt;code&gt;--armor&lt;/code&gt; flag.&lt;/p&gt;
&lt;p&gt;When you send your recipient the file, you just need to send them the &lt;code&gt;file.tar.gz.asc&lt;/code&gt; file. They can verify it using the command below, and decrypt (unpack) it using the command above, without the need for your private key. (This is because the file is merely compressed and signed - it’s not encrypted with your public key.)&lt;/p&gt;
&lt;p&gt;However, this method has problems. What if you are sending an email, and want your recipient to be able to read it immediately, without decrypting the file?&lt;/p&gt;
&lt;p&gt;Well, you can send your recipient a cleartext file signed with your key:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --clearsign&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; file.txt&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is used when you’re sending plaintext documents, like emails. The plaintext message is first shown, and the signature follows shortly after.&lt;/p&gt;
&lt;p&gt;That’s great and all, but what about big files? You don’t want to necessarily create another file for the signature that will take up just as much as the original file! This is where “detached” signature files come in. Most Linux distributions and other big library/framework projects use this method for distribution. You create a detached signature using this command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --detach-sig&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; file.iso&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;“Wait,” you say. “What’s the difference between this and the first command?”&lt;/p&gt;
&lt;p&gt;The first command compresses the entire file, signs it, and then spits out a GPG file based off of that. This means that transport becomes easier, because the signature and the file becomes one file. However, the method requires you to pack the file and spend resources making a new file, and the method requires the recipient to use as much resources to unpack the signature file to get the original file.&lt;/p&gt;
&lt;p&gt;However, this “detach” command creates a signature file that is only a couple of kilobytes in size. This in turn is used to verify the original file. This is what you see when you see files on Linux distribution websites - “Ubuntu.iso” and “Ubuntu.iso.sig” or “Ubuntu.iso.asc”. The latter two verifies the first file, and they are only a couple of kilobytes in size.&lt;/p&gt;
&lt;p&gt;In comparison, if you used the first command to generate the signature, GPG will actually warn you that the signature is NOT a detached signature, and therefore GPG has no way of verifying the original file. You will have to unpack the signed file and then compare checksums between the two file to make sure they are not tampered with. But at that point you don’t need to download the original file. This is why the first command should be reserved for files of small size.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; - Clearsign for emails and plaintext files, detach for big files (like .isos), and finally compress and sign for regular files (couple of megabytes).&lt;/p&gt;
&lt;h1 id=&quot;verifying-files&quot;&gt;Verifying files&lt;/h1&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --verify&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; file.tar.gz.gpg&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Thanks for following along on this long article! I hope you learned a thing or two about GPG and keys and signing and encryption.&lt;/p&gt;</content:encoded></item><item><title>Setting up Duplicacy on UnRAID with Backblaze B2</title><link>https://ericswpark.com/blog/2019/2019-11-10-setting-up-duplicacy-on-unraid-with-backblaze-b2/</link><guid isPermaLink="true">https://ericswpark.com/blog/2019/2019-11-10-setting-up-duplicacy-on-unraid-with-backblaze-b2/</guid><pubDate>Sun, 10 Nov 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In this post we’re going to look at Duplicacy, a tool that allows you to backup large amounts of data to any storage provider.&lt;/p&gt;
&lt;h1 id=&quot;so-what-is-duplicacy&quot;&gt;So what is Duplicacy?&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://duplicacy.com/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;Duplicacy&lt;/a&gt; is a backup/restore tool for servers. They have a free CLI version and a paid GUI version, but you probably only need the CLI tool unless you’re extremely scared of the terminal.&lt;/p&gt;
&lt;h1 id=&quot;big-warning-before-we-begin&quot;&gt;Big warning before we begin&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Duplicacy expects sole access to the backup destination. That means, if a backup is running and you issue a prune command on another server, it may result in &lt;strong&gt;a loss of ALL of your data!&lt;/strong&gt; Always make sure that &lt;strong&gt;only one instance of Duplicacy is operating at a given moment&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;RAID is not a backup. If someone tells you they have a bulletproof backup solution with RAID only, you have the right to laugh in their face.&lt;/li&gt;
&lt;li&gt;If you can’t restore it, you don’t have it. If you have everything stored on Amazon Glacier and your house burns down, you’ll probably spend all your money on a new house, not for downloading your music stash from Glacier. Always make sure the backup is in an easily-accessible medium and make sure to test your backup so that you don’t have trouble restoring it later.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Onto the guide!&lt;/p&gt;
&lt;h1 id=&quot;steps-to-install&quot;&gt;Steps to install&lt;/h1&gt;
&lt;p&gt;Now, UnRAID boots off of USB drives, which means that if you just blindly copy your Duplicacy executable to a system folder, the changes will be lost on next boot. So we need to install Duplicacy on a persistent partition, and then copy it across.&lt;/p&gt;
&lt;p&gt;Navigate to your USB drive on your UnRAID server:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /boot/config/plugins&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Download the Duplicacy executable:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;wget&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -O&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; duplicacy&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; https://github.com/gilbertchen/duplicacy/releases/download/v2.2.3/duplicacy_linux_x64_2.2.3&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now it’s very certain that there is a new version by the time you are reading this article. Simply go to &lt;a href=&quot;https://github.com/gilbertchen/duplicacy/releases&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;the releases tab on GitHub&lt;/a&gt;, right click on &lt;code&gt;duplicacy_linux_x64_xxx&lt;/code&gt; where &lt;code&gt;xxx&lt;/code&gt; is the version number, and select “Copy link location” (may vary depending on browser). Then paste it after &lt;code&gt;wget -O duplicacy&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Then, make the necessary changes in your &lt;code&gt;go&lt;/code&gt; file to copy over the executable on every boot. Type the following two lines at the end of &lt;code&gt;/boot/config/go&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Copy duplicacy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ln -s /boot/config/plugins/duplicacy /usr/local/bin/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This should symlink the executable to an executable location every time UnRAID starts up.&lt;/p&gt;
&lt;p&gt;You’re done! Reboot the machine (or run &lt;code&gt;ln -s /boot/config/plugins/duplicacy /usr/local/bin/&lt;/code&gt;) and make sure you can run &lt;code&gt;duplicacy -h&lt;/code&gt; in your terminal.&lt;/p&gt;
&lt;h1 id=&quot;setting-up-a-duplicacy-repository&quot;&gt;Setting up a Duplicacy “repository”&lt;/h1&gt;
&lt;p&gt;You need to get Duplicacy to &lt;code&gt;init&lt;/code&gt; a new “repository”. This means you’re marking what you want to back up. Duplicacy will create a new hidden folder called &lt;code&gt;.duplicacy&lt;/code&gt; in your current directory.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;duplicacy&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; init&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; repositoryname&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; b2://servername-duplicacy&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -e&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So what does this mean? Let’s go over this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Let’s say we’re currently in &lt;code&gt;/mnt/user&lt;/code&gt;, so we’re backing up everything underneath it. For example, Duplicacy will back up &lt;code&gt;/mnt/user/Movies&lt;/code&gt;, &lt;code&gt;/mnt/user/Music&lt;/code&gt;, and so on.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;init&lt;/code&gt; tells Duplicacy we want to set up a repository here.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;repositoryname&lt;/code&gt; is just a name you will give to the repository. Set it to whatever.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;b2://&lt;/code&gt; is the protocol (Backblaze B2). You can use other protocls if you use different providers, such as &lt;code&gt;sftp://&lt;/code&gt; and so on.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;servername-duplicacy&lt;/code&gt; is the bucket name you have in B2. You should already have this created before running the command!&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-e&lt;/code&gt; tells Duplicacy to encrypt the backups.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can actually set up duplicacy in the root folder of your shares, &lt;code&gt;/mnt/user/*&lt;/code&gt;. This way, &lt;code&gt;duplicacy&lt;/code&gt; stores all the configuration and other details in &lt;code&gt;/mnt/user/.duplicacy/*&lt;/code&gt;.&lt;/p&gt;
&lt;h1 id=&quot;stop-duplicacy-asking-for-keys&quot;&gt;Stop Duplicacy asking for keys&lt;/h1&gt;
&lt;p&gt;The next step to automation is to get Duplicacy to stop asking for your application ID and password all the time. Otherwise, you will need to manage them yourself every time you back up, which will not work in an automated environment.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; If you go through with these commands, then your application keys and encryption password will be stored as plain-text inside your &lt;code&gt;.duplicacy&lt;/code&gt; folder. &lt;strong&gt;Anyone with access to this folder will be able to see your keys.&lt;/strong&gt; Only issue the following commands if you are certain that only you will have access to the server.&lt;/p&gt;
&lt;p&gt;Run the following commands:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;duplicacy&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; set&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -storage&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; b2://servername-duplicacy&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -key&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; b2_id&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -value&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; b2_id_here&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;duplicacy&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; set&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -storage&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; b2://servername-duplicacy&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -key&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; b2_key&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -value&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; b2_key_here&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;duplicacy&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; set&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -storage&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; b2://servername-duplicacy&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -key&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; password&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -value&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; encryption_key_here&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;excluding-folders-and-files&quot;&gt;Excluding folders and files&lt;/h1&gt;
&lt;p&gt;You may want to customize the filters file in &lt;code&gt;.duplicacy/&lt;/code&gt; before running the backup. Personally, I have mine set to exclude Docker volume folders, VM folders, and the Docker image (since they can always be recreated with no loss).&lt;/p&gt;
&lt;p&gt;For the full documentation, &lt;a href=&quot;https://forum.duplicacy.com/t/filters-include-exclude-patterns/1089&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;refer to the official documentation&lt;/a&gt; since I don’t know regular expressions yet.&lt;/p&gt;
&lt;p&gt;Here is my UnRAID &lt;code&gt;filters&lt;/code&gt; file if you are interested:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-appdata/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-backup/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-VM/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-Downloads/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-ISO/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-docker.img&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-libvrt.img&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;automating-backups&quot;&gt;Automating backups&lt;/h1&gt;
&lt;p&gt;You’re almost there! We need to automate backups with User Scripts, since we all know you won’t manually remote every day to issue the command.&lt;/p&gt;
&lt;p&gt;Download User Scripts (plenty of guides online) on your UnRAID server. You can use Community Applications for it. Once you’re there, create a script with the following:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#!/bin/bash&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# https://stackoverflow.com/a/185473/1388019&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;lockfile&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;/tmp/duplicacy.lock&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [ &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-e&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ${lockfile} ] &amp;#x26;&amp;#x26; &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;kill&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -0&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;cat&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;lockfile&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    echo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;duplicacy already running&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    exit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;fi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# make sure the lockfile is removed when we exit and then claim it&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;trap&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;rm -f ${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;lockfile&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}; exit&quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; INT&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; TERM&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; EXIT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; $$&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ${lockfile}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# run the backup with default settings&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /mnt/user&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;/usr/local/bin/duplicacy&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -log&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; backup&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -threads&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -stats&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# clean up lockfile&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;rm&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -f&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ${lockfile}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s go over this line by line. On the first couple of lines, from the line that starts with &lt;code&gt;lockfile&lt;/code&gt; to &lt;code&gt;echo $$&lt;/code&gt;, we set up a lockfile. In case User Scripts mess up and run the script twice (again, two instances of Duplicacy running can seriously corrupt the backup), the &lt;code&gt;lockfile&lt;/code&gt; prevents the script from running.&lt;/p&gt;
&lt;p&gt;Then the two lines after that start the backup, with 2 threads. Adjust to match your CPU. &lt;code&gt;-stats&lt;/code&gt; just shows you what it’s backing up at a given moment.&lt;/p&gt;
&lt;p&gt;Save it with an appropriate name and description, set a schedule, and let it run in the background! The backup is now completely automated!&lt;/p&gt;
&lt;h1 id=&quot;deleting-old-backups&quot;&gt;Deleting old backups&lt;/h1&gt;
&lt;p&gt;By the way, Duplicacy will not automatically delete old backups. To do that, you must &lt;code&gt;prune&lt;/code&gt; your backup set.&lt;/p&gt;
&lt;p&gt;Add the following two lines in the original script, before the “clean up lockfile” section:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Delete any backups older than 120 days. Adjust if required&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;duplicacy&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; prune&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -exhaustive&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -exclusive&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -keep&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 0:120&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This deletes any previous data backups older than 120 days. Adjust the date if necessary.&lt;/p&gt;
&lt;h1 id=&quot;how-do-i-restore&quot;&gt;How do I restore?&lt;/h1&gt;
&lt;p&gt;I’m not sure because my server hasn’t crashed yet. (knock on wood)&lt;/p&gt;
&lt;p&gt;If it ever does I will update this section. But it should just be a matter of running something like &lt;code&gt;duplicacy restore&lt;/code&gt; in the root directory after restoring your configuration files.&lt;/p&gt;
&lt;h1 id=&quot;to-uninstall&quot;&gt;To uninstall&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;Delete the executable over at &lt;code&gt;/boot/config/plugins/duplicacy&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Edit the go file over at &lt;code&gt;/boot/config/go&lt;/code&gt;. Remove the two lines that we added above.&lt;/li&gt;
&lt;li&gt;Delete configuration files if required. Go to your source directory (for me it’s &lt;code&gt;/mnt/user&lt;/code&gt;) and then delete the &lt;code&gt;.duplicacy&lt;/code&gt; directory.&lt;/li&gt;
&lt;li&gt;Remove the User Scripts.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once that’s done, reboot your UnRAID server.&lt;/p&gt;</content:encoded></item><item><title>Find your CPU model on macOS</title><link>https://ericswpark.com/blog/2019/2019-07-29-find-your-cpu-model-on-macos/</link><guid isPermaLink="true">https://ericswpark.com/blog/2019/2019-07-29-find-your-cpu-model-on-macos/</guid><pubDate>Mon, 29 Jul 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When you go to Apple’s website to buy a new Mac, most promotional materials say something along the lines of “featuring Intel’s 10th-generation processors with eight-cores that turbo-boost up to 3.2 GHz”. OK, but what is the CPU’s model name?! Even if you dig into Apple’s detailed spec sheets, the model number is strangely missing. Almost like they don’t want you to find what it is.&lt;/p&gt;
&lt;p&gt;However, you can find the Intel CPU model in your Mac by running this command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sysctl&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; machdep.cpu.brand_string&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This usually prints something like:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Why can’t Apple tell me this on the spec sheet?!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update (2020)&lt;/strong&gt;: Obviously this post is no longer relevant as Apple now lists the Apple Silicon revision on their website. I guess it’d still help if you have an old Intel Mac.&lt;/p&gt;</content:encoded></item><item><title>Bypassing NAT with reverse SSH tunneling</title><link>https://ericswpark.com/blog/2018/2018-08-20-bypassing-nat-with-reverse-ssh-tunneling/</link><guid isPermaLink="true">https://ericswpark.com/blog/2018/2018-08-20-bypassing-nat-with-reverse-ssh-tunneling/</guid><pubDate>Mon, 20 Aug 2018 00:00:00 GMT</pubDate><content:encoded>&lt;h1 id=&quot;problem&quot;&gt;Problem&lt;/h1&gt;
&lt;p&gt;I had recently moved to China, where I discovered that the carriers used CGNAT to combat the growing problem of shrinking IPv4 assignments. No surprises there, since China is literally booming with people. However, this posed a big problem for my home server and network, since in Korea I had my very own public IP address handed out to me.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;nat-1&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;4032&quot; height=&quot;3024&quot; src=&quot;/_astro/nat-1.DtaeiAHk_ZKS06L.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;But there is always a solution - and this time, the solution came in the form of SSH. Using SSH, I could forward literally any ports I wanted, from my local network, to a remote server that did have a public facing IP address.&lt;/p&gt;
&lt;p&gt;In this tutorial, I’ll go over how to get your own remote server set up, and tunnel your very own home server to a remote server so that you could access it from anywhere.&lt;/p&gt;
&lt;h1 id=&quot;1-digitalocean&quot;&gt;1. DigitalOcean&lt;/h1&gt;
&lt;p&gt;You need a remote server to get started. I chose a VPS, because it is simple and easy to just spin up an instance. Hop onto DigitalOcean, make an account, link a credit card, and spin up an instance. Size doesn’t really matter - I chose the smallest instance on there, which is $5 a month.&lt;/p&gt;
&lt;p&gt;Note down the IP address of your instance. From now on, I will be expressing this IP as &lt;code&gt;XXX.XXX.XXX.XXX&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;nat-2&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;4032&quot; height=&quot;3024&quot; src=&quot;/_astro/nat-2.Dvjl-FvT_159aip.webp&quot; &gt;&lt;/p&gt;
&lt;h1 id=&quot;2-server-setup&quot;&gt;2. Server setup&lt;/h1&gt;
&lt;p&gt;Connect to your server using &lt;code&gt;ssh&lt;/code&gt;. Do any server-maintenance stuff you need to do. I recommend this &lt;a href=&quot;https://www.digitalocean.com/community/tutorials/how-to-create-a-sudo-user-on-ubuntu-quickstart&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;quick start article from DigitalOcean to add a sudo user to your server.&lt;/a&gt; After you’ve set up your server and secured it enough, proceed to the next step. I won’t be going into detail on server hardening, because that’s a topic for another day.&lt;/p&gt;
&lt;p&gt;Then, you want to modify &lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt;, assuming you chose Ubuntu when you set up your VPS instance. Inside there, find the line named &lt;code&gt;#GatewayPorts no&lt;/code&gt; and change it to &lt;code&gt;GatewayPorts yes&lt;/code&gt;(remove the hash in the front and change &lt;code&gt;no&lt;/code&gt; to &lt;code&gt;yes&lt;/code&gt;.)&lt;/p&gt;
&lt;p&gt;Save, exit, and type &lt;code&gt;sudo service sshd restart&lt;/code&gt;. In theory you shouldn’t get kicked off from the server, but if you do, just reconnect. Finally, restart the server.&lt;/p&gt;
&lt;h1 id=&quot;3-client-setup&quot;&gt;3. Client setup&lt;/h1&gt;
&lt;p&gt;Let’s say you have a port you want to forward on localhost, port 9000. You want to make it so that when you connect to the remote server, you could access it on port 80.&lt;/p&gt;
&lt;p&gt;Client —&gt; Remote server &lt;code&gt;XXX.XXX.XXX.XXX&lt;/code&gt; (port &lt;code&gt;80&lt;/code&gt;) —&gt; Home Server —&gt; Web instance (port &lt;code&gt;9000&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;nat-3&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;4032&quot; height=&quot;3024&quot; src=&quot;/_astro/nat-3.Du7_BJH0_1BoLt5.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;To do that, on the &lt;strong&gt;home server&lt;/strong&gt; type this SSH command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ssh&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -nNT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -R&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 80:localhost:9000&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; username@XXX.XXX.XXX.XXX&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s dissect this command. &lt;code&gt;ssh&lt;/code&gt; is the program name itself, &lt;code&gt;-nNT&lt;/code&gt; instructs SSH to not create a TTY instance (so you will NOT get a Terminal prompt), &lt;code&gt;-R&lt;/code&gt; tells SSH we want to make a remote port forward, &lt;code&gt;80&lt;/code&gt; is the port the remote server will be listening on, &lt;code&gt;localhost&lt;/code&gt; is the IP the home server will forward requests to (in this case, it’ll be itself), &lt;code&gt;9000&lt;/code&gt; would be the port that the home server would listen on, and the rest (&lt;code&gt;username@XXX.XXX.XXX.XXX&lt;/code&gt;) is just the typical SSH details to connect to a server.&lt;/p&gt;
&lt;p&gt;Boiling all this down to a practical example, I have a instance with an IP address of &lt;code&gt;192.168.0.102&lt;/code&gt; on my local network, such as a Raspberry Pi. It is listening on port &lt;code&gt;12984&lt;/code&gt; and is hosting a web server. I want to see my website pop up when I connect to the remote server, so we would have to use port &lt;code&gt;80&lt;/code&gt; or even port &lt;code&gt;443&lt;/code&gt;. To access this through a reverse tunnel, I would have to use this command on my home server:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ssh&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -nNT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -R&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 80:192.168.0.102:12984&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; username@XXX.XXX.XXX.XXX&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; # HTTP&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ssh&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -nNT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -R&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 443:192.168.0.102:12984&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; username@XXX.XXX.XXX.XXX&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; # HTTPS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ssh&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -nNT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -R&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 80:192.168.0.102:12984&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -R&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 443:192.168.0.102:12984&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; username@XXX.XXX.XXX.XXX&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; # or all in one&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And this would be the diagram for how it would work:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;nat-4&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;4032&quot; height=&quot;3024&quot; src=&quot;/_astro/nat-4.BoT-wsFj_1BiNOk.webp&quot; &gt;&lt;/p&gt;
&lt;h1 id=&quot;4-autossh&quot;&gt;4. AutoSSH&lt;/h1&gt;
&lt;p&gt;Sure, you could type these commands out every time you want a bridge! But personally, I’m lazy, and I want a script that does it for me every boot.&lt;/p&gt;
&lt;p&gt;That’s where AutoSSH comes in.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; apt&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; autossh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Change to fit your distribution. In this case, I’m using Ubuntu Server. Then, we need to make a &lt;code&gt;systemd&lt;/code&gt; configuration file.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;nano&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /etc/systemd/system/autossh-tunnel.service&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Paste the following in, adapting it for your use:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[Unit]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Description=AutoSSH tunnel service&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;After=network.target&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[Service]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Environment=&quot;AUTOSSH_GATETIME=0&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ExecStart=/usr/bin/autossh -M 0 -o &quot;ServerAliveInterval 30&quot; -o &quot;ServerAliveCountMax 3&quot; -o &quot;ExitOnForwardFailure yes&quot; -N -R 80:192.168.0.1:80 -R 443:192.168.0.1:443 root@server.example.com -p 22 -i /home/example/.ssh/private-key&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[Install]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;WantedBy=multi-user.target&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then tell &lt;code&gt;systemd&lt;/code&gt; to look for new configuration, and then enable the configuration:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; systemctl&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; daemon-reload&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; systemctl&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; start&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; autossh-tunnel.service&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; systemctl&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; enable&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; autossh-tunnel.service&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you want to configure how SSH connects to the server, you could utilize &lt;code&gt;~/.ssh/config&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Host example-tunnel&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    HostName server.example.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    User example&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    Port 22&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    IdentityFile ~/.ssh/private-key&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    RemoteForward 80 192.168.0.1:80&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    RemoteForward 443 192.168.0.1:443&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ServerAliveInterval 30&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ServerAliveCountMax 3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ExitOnForwardFailure yes&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You don’t really need to specify &lt;code&gt;RemoteForward&lt;/code&gt; here since AutoSSH will take care of that for you anyway, but I just put it there for peace of mind.&lt;/p&gt;
&lt;p&gt;For more information, &lt;a href=&quot;https://www.everythingcli.org/ssh-tunnelling-for-fun-and-profit-autossh/&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;you could check out this great guide over here&lt;/a&gt;. I learnt a lot from that post, so thanks to the authors of that guide!&lt;/p&gt;</content:encoded></item><item><title>Force git to sign your commits</title><link>https://ericswpark.com/blog/2018/2018-05-13-force-git-to-sign-your-commits/</link><guid isPermaLink="true">https://ericswpark.com/blog/2018/2018-05-13-force-git-to-sign-your-commits/</guid><pubDate>Sun, 13 May 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Since GitHub puts a nice shiny “Verified” next to your commits if you sign them with GPG, you might be wondering how to get on this bandwagon. Not to worry! With this one simple trick, you’ll never have to remember to type &lt;code&gt;git -S&lt;/code&gt;, ever again!&lt;/p&gt;
&lt;p&gt;Run the following (substitute your key in the second command):&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; config&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --global&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; commit.gpgsign&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; config&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --global&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; user.signingkey&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;KEYHER&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;E&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Did you forget your GPG keys?&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --list-keys&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --keyid-format&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; short&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alternatively, &lt;a href=&quot;/blog/2020/2020-02-25-gpg-the-complete-crash-course/&quot;&gt;check out my comprehensive GPG guide.&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&quot;troubleshooting&quot;&gt;Troubleshooting&lt;/h1&gt;
&lt;p&gt;If &lt;code&gt;git&lt;/code&gt; fails to sign your commit with the following error:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;error: gpg failed to sign the data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;fatal: failed to write commit object&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is usually due to &lt;code&gt;gpg&lt;/code&gt; not being able to find the current shell to launch the password prompt on. To fix this problem, append the following line in your &lt;code&gt;~/.bashrc&lt;/code&gt; or &lt;code&gt;~/.zshrc&lt;/code&gt;, depending on what shell you use:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; GPG_TTY&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;tty&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I’ve also seen this error occur on Visual Studio Code if the terminal window’s height is too small for the prompt. Resize the terminal to be longer and it should fix the problem.&lt;/p&gt;
&lt;h2 id=&quot;windows&quot;&gt;Windows&lt;/h2&gt;
&lt;p&gt;If you are using GPG4Win to manage your GPG keys, you may need to run the following line in the terminal as well:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;git config --global gpg.program &quot;C:\Program Files (x86)\GnuPG\bin\gpg.exe&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This makes &lt;code&gt;git&lt;/code&gt; use GPG4Win’s &lt;code&gt;gpg.exe&lt;/code&gt; executable instead of the one bundled with Git for Windows.&lt;/p&gt;</content:encoded></item><item><title>Set up a network wide VPN using Ubuntu Server</title><link>https://ericswpark.com/blog/2018/2018-05-01-set-up-a-network-wide-vpn-using-ubuntu-server/</link><guid isPermaLink="true">https://ericswpark.com/blog/2018/2018-05-01-set-up-a-network-wide-vpn-using-ubuntu-server/</guid><pubDate>Tue, 01 May 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As a foreigner living in China, you crave Netflix and YouTube, but hate toggling over to your VPN app over and over just to watch censored content. So waht’s the solution?&lt;/p&gt;
&lt;p&gt;Enter the network-wide VPN gateway - your very own server that you will set up to tunnel your entire home’s network!&lt;/p&gt;
&lt;h1 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h1&gt;
&lt;p&gt;You will need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A VPN provider that gives out OpenVPN configurations. Other protocols will probably also work, but they are not covered in this tutorial.&lt;/li&gt;
&lt;li&gt;A server or a VM running Ubuntu Server. Using the latest version is recommended. I used 16.04 LTS at the time of writing this blog post.&lt;/li&gt;
&lt;li&gt;Some Linux and networking knowledge&lt;/li&gt;
&lt;li&gt;(optional, but recommended) A router that &lt;strong&gt;supports&lt;/strong&gt; setting the default gateway in the DHCP server&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Without proper Linux and networking knowledge, you will have a broken LAN someday with no way to troubleshoot or debug! Make sure you know what you are doing before following this tutorial.&lt;/p&gt;
&lt;p&gt;A router that supports setting the default gateway on the DHCP server is important. Without it, you will have to manually go around each device and set up the proper configuration so it’ll route the traffic through your new Linux router! At that point, you might as well set up the VPN on each device, but the benefit of setting this up at all without a supported router is still great that you should consider setting it up anyway. Some devices that do not support native VPN (such as the Chromecast, Roku Fire sticks, your smart robot cleaner) will now connect through the VPN through a simple configuration change.&lt;/p&gt;
&lt;p&gt;So whether you’re serious about this, or it’s just a fun experiment, it’s definitely worth it to set it up. Just understand the risks before you move on. If any problems arise, you should be able to debug them.&lt;/p&gt;
&lt;h1 id=&quot;vpn-setup&quot;&gt;VPN setup&lt;/h1&gt;
&lt;p&gt;Grab the OpenVPN file from your VPN provider. The page should also list your unique username and password for OpenVPN. Grab that too.&lt;/p&gt;
&lt;p&gt;(NOTE: If it does not show your unique username and password, then it might be your VPN provider’s account username and password. Experiment and see if it works.)&lt;/p&gt;
&lt;p&gt;Now, depending on your VPN provider, they will give you one, in-line OpenVPN file, or separated files consisting of the client keys and server certificates. The former is better, since you won’t have to monkey around with key configurations.&lt;/p&gt;
&lt;p&gt;Jumping onto your VM, execute this command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ifconfig&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will show your current network configuration. Note down the interface name on the left side. For example, my network was:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;enp2s0    Link encap:Ethernet  HWaddr &amp;#x3C;obfuscated&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            inet addr:192.168.0.100  Bcast:192.168.0.255  Mask:255.255.255.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            &amp;#x3C;truncated&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;lo        Link encap:Local Loopback  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            inet addr:127.0.0.1  Mask:255.0.0.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            &amp;#x3C;truncated&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Some things to note here. First, your Ethernet interface is &lt;code&gt;enp2s0&lt;/code&gt;, and your IP address is &lt;code&gt;192.168.0.100&lt;/code&gt;. These will be important later on.&lt;/p&gt;
&lt;p&gt;Now, let’s install OpenVPN:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; apt&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; openvpn&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once it’s installed, let’s configure it. Copy the sample configuration file:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; cp&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;VPN&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; name&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; her&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.ovpn&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /etc/openvpn/&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;VPN&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; name&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; her&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.conf&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point, if you have keys included with the &lt;code&gt;.ovpn&lt;/code&gt; file, look at the keys. They will be named like &lt;code&gt;ca.rsa.2048.crt&lt;/code&gt; or similar, depending on what key size your VPN provider uses. Simply find the key files and copy them with this command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; cp&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ca.rsa.2048.crt&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; crl.rsa.2048.pem&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /etc/openvpn&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If your provider is smart enough to provide inline certificate files (OpenVPN configuration files that have the certificates attached inside), you can skip the above command. Continue down below.&lt;/p&gt;
&lt;p&gt;Now you might be wondering why we renamed that &lt;code&gt;.ovpn&lt;/code&gt; file to &lt;code&gt;.conf&lt;/code&gt;. It’s because this is the only way we can use &lt;code&gt;systemd&lt;/code&gt; to control OpenVPN. With this change, the configuration file gets registered to &lt;code&gt;systemctl&lt;/code&gt;, and you can enable them with &lt;code&gt;OpenVPN@VPN-name-here&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Next, note down your VPN username and password in &lt;code&gt;/etc/openvpn/login&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;username-here&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;password-here&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;An example credential file would be something like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ericswpark&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;examplepassword12345&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Save and exit. Now edit &lt;code&gt;/etc/openvpn/&amp;#x3C;VPN name here&gt;.conf&lt;/code&gt; and look for the following line:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;auth-user-pass&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Change it to:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;auth-user-pass /etc/openvpn/login&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, if you have separate key files copied from above, you will need to do one more step. Find these lines:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ca ca.rsa.2048.crt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;crl-verif crl.rsa.2048.pem&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And change them into:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ca /etc/openvpn/ca.rsa.2048.crt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;crl-verif crl.rsa.2048.pem&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And you’re done!&lt;/p&gt;
&lt;p&gt;Before you save, there’s some additional options you might want to configure. For example, you might want to disable &lt;code&gt;persist-tun&lt;/code&gt; if you live in an environment where Internet connection is flaky. The VPN will destroy the tunnel interface when it restarts, forcing it to use your regular censored Internet for the VPN server lookup. This is handy because the VPN server lookup will fail over a dead tunnel interface.&lt;/p&gt;
&lt;p&gt;Find this line:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;persist-tun&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And put a &lt;code&gt;#&lt;/code&gt; in front of it to disable it:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# persist-tun&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you want the server to keep the connection alive, find or add these following two lines:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;keepalive 10 30&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;resolv-retry infinite&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The last line isn’t strictly needed, because the default is infinite. The first line tells the OpenVPN client to ping the server (&lt;code&gt;--ping&lt;/code&gt;) every 10 seconds, and restart (&lt;code&gt;--ping-restart&lt;/code&gt;) if there is no response for 30 seconds. This has been handy because the Great Firewall of China likes to send TCP RST packets everyonce in a while. Experiment with those values if you need a more reliable connection, but I’ve found 30 seconds is pretty adequate for a smooth network drop fix without unnecessarily straining the VPN server.&lt;/p&gt;
&lt;p&gt;Save and now, we’ll have to test the VPN server. Since I’m in China, that’s super easy! Just ping Google. If you’re in a different country where that test is inconclusive, you could check &lt;code&gt;ifconfig&lt;/code&gt; again.&lt;/p&gt;
&lt;p&gt;Start the client:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; openvpn&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --config&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /etc/openvpn/&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;VPN&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; name&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; her&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.conf&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you see &lt;code&gt;Initialization Sequence Completed&lt;/code&gt; anywhere in the logs, congratulations! Your VPN is set up correctly! Press Ctrl-C to destroy the VPN tunnel and follow along with next steps.&lt;/p&gt;
&lt;h1 id=&quot;setting-up-a-static-ip-optional-but-recommended&quot;&gt;Setting up a static IP (optional, but recommended)&lt;/h1&gt;
&lt;p&gt;At this point, you might want to set up a static IP for your VM. This is because you don’t want IPs shifting if you decide to add the IP to your default gateway settings in your router.&lt;/p&gt;
&lt;p&gt;Log into your router administration page. The details should be underneath the router, printed out on a sticker. If you can’t find it, then just Google your router’s manufacturer and look for the IP. If that doesn’t work, check your computer’s default gateway, because usually routers push their IP through there. If all else fails then just type in addresses like &lt;code&gt;192.168.0.1&lt;/code&gt; or &lt;code&gt;192.168.1.1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you find the IP, remember to write it down because it’ll be important later, and click on DHCP address reservations, or something named similar. Remember your MAC address from step 0? No? Then just do &lt;code&gt;ifconfig&lt;/code&gt; again and jot down the MAC address for your Ethernet interface.&lt;/p&gt;
&lt;p&gt;Assign it an address, then press OK. Remember that address, Now some more configurations on your VM! Edit this file, &lt;code&gt;/etc/network/interfaces&lt;/code&gt; and find the lines:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;auto enp2s0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;iface enp2s0 inet dhcp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The interface name might be different. Just change it so that it looks like this (remember to keep the interface name that was in your file, NOT the ones shown here!):&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;auto enp2s0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;iface enp2s0 inet static&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    address 192.168.0.100&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    netmask 255.255.255.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    gateway 192.168.0.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    dns-nameservers 1.1.1.1 1.0.0.1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You’re free to use any DNS nameservers you like. Personally, I like the new CloudFlare &lt;code&gt;1.1.1.1&lt;/code&gt;s. Remember to input the correct IP address and netmask! The IP address is the one you manually assigned on the router administration page, the netmask can be found by checking your network settings, and the gateway should be whatever it was before (check network settings on your computer) or your router’s administration IP, which is usually the default gateway on most routers.&lt;/p&gt;
&lt;p&gt;The default gateway bit is important, because if you decide to forward the VM’s IP as the default gateway later in the DHCP push settings, your VM box will have a default gateway value set to itself. So make sure the default gateway is the current default gateway shown in your network settings.&lt;/p&gt;
&lt;p&gt;Cool! Reboot the VM and the router and make sure both are accessible, and your Internet is working. Next!&lt;/p&gt;
&lt;h1 id=&quot;setting-up-the-tunnel&quot;&gt;Setting up the tunnel&lt;/h1&gt;
&lt;p&gt;First, let’s enable the OpenVPN service so it starts at boot.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; systemctl&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; enable&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; OpenVPN@&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;VPN&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; name&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; her&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Don’t write the &lt;code&gt;.conf&lt;/code&gt; extension when you’re running this command! Then, let’s enable IPv4 forwarding. Edit this file -  &lt;code&gt;/etc/sysctl.conf&lt;/code&gt; - and find this line:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# net.ipv4.ip_forward = 1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Change it to:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;net.ipv4.ip_forward = 1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Just remove that &lt;code&gt;#&lt;/code&gt;, save and move on.&lt;/p&gt;
&lt;p&gt;Next, reload &lt;code&gt;sysctl&lt;/code&gt;!&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; sysctl&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -p&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Congratulations! Now we need to configure the firewall and routing.&lt;/p&gt;
&lt;h1 id=&quot;iptables&quot;&gt;&lt;code&gt;iptables&lt;/code&gt;&lt;/h1&gt;
&lt;p&gt;This part is extremely hard, probably the hardest in this tutorial. I don’t even understand most of it, but will try to explain it as best as I could.&lt;/p&gt;
&lt;p&gt;First off, we want to allow the loopback traffic through the loopback device. Execute these two commands:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; iptables&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -A&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; INPUT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -i&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; lo&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -m&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; comment&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --comment&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;loopback-input&quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -j&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ACCEPT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; iptables&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -A&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; OUTPUT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; lo&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -m&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; comment&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --comment&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;loopback-output&quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -j&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ACCEPT&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, we want to allow traffic coming in from the network, and traffic going out to the tunnel network. (If you don’t know these or forgot from earlier, run the VPN using the command way above, and run &lt;code&gt;ifconfig&lt;/code&gt; to figure out the interfaces.) Execute these commands, making sure to substitute the interface names from earlier:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; iptables&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -I&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; INPUT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -i&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; enp2s0&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -m&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; comment&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --comment&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Local network&quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -j&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ACCEPT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; iptables&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -I&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; OUTPUT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; tun0&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -m&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; comment&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --comment&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;VPN network&quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -j&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ACCEPT&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we need to allow the OpenVPN traffic to pass through the box. Find the port number by looking at the &lt;code&gt;.conf&lt;/code&gt; file we talked about earlier. It should be on the line that starts with &lt;code&gt;remote&lt;/code&gt;. Then execute this command, making sure to replace that port number and interface name:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; iptables&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -A&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; OUTPUT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; enp2s0&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -p&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; udp&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --dport&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1194&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -m&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; comment&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --comment&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;OpenVPN traffic&quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -j&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ACCEPT&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next up, we need to allow certain services to operate. Execute these commands:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; iptables&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -A&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; OUTPUT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; eth0&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -p&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; udp&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --dport&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 123&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -m&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; comment&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --comment&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;NTP service&quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -j&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ACCEPT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; iptables&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -A&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; OUTPUT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -p&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; UDP&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --dport&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 67:68&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -m&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; comment&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --comment&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;DHCP service&quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -j&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ACCEPT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; iptables&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -A&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; OUTPUT&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; eth0&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -p&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; udp&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --dport&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 53&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -m&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; comment&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --comment&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;DNS service&quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -j&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ACCEPT&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Almost there! You need to forward the traffic from the tunnel to the Ethernet interface. This bit is really important! Run these commands (and make sure to substitute the interface names):&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; iptables&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -A&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; FORWARD&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -i&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; tun0&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; enp2s0&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -m&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; state&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --state&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; RELATED,ESTABLISHED&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -j&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ACCEPT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; iptables&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -A&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; FORWARD&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -i&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; enp2s0&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; tun0&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -m&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; comment&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --comment&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Local network to VPN&quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -j&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ACCEPT&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Cool, one last thing. You need to set up &lt;code&gt;MASQUERADE&lt;/code&gt; on the &lt;code&gt;POSTROUTING&lt;/code&gt; table on your NAT, so that the Network Address Translation works across the VPN. Execute the final command (switch these interface names, remember):&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; iptables&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -t&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; nat&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -A&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; POSTROUTING&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; tun0&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -j&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; MASQUERADE&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you managed to follow this far, congratulations! But before you go and celebrate, you want to save these configurations so these don’t get lost over a reboot.&lt;/p&gt;
&lt;p&gt;Run this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; apt&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; iptables-persistent&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And the installation will begin. If it asks you if it should save the current configuration, choose yes, and carry on. Let’s enable the service to run at boot:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; systemctl&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; enable&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; netfilter-persistent&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If, in the far future, you add any more &lt;code&gt;iptables&lt;/code&gt; rules, you can save them by running:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; netfilter-persistent&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; save&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And they will be saved.&lt;/p&gt;
&lt;p&gt;One last, really important thing. If you want to back up these configurations, you can type:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; iptables-save&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; backup.txt&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To save the rules inside a text file. This is also a handy way to edit these configurations should you decide to change them. Once you want to restore, use this command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; iptables-restore&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; backup.txt&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nice! Your configuration is nearly finished!&lt;/p&gt;
&lt;h1 id=&quot;test-the-connection&quot;&gt;Test the connection&lt;/h1&gt;
&lt;p&gt;Reboot the box. On the client device you want to test, change the default gateway’s IP address to the VM’s IP. It should start forwarding traffic through the VPN! Congratulations!&lt;/p&gt;
&lt;p&gt;But this is tedious. I’m lazy. What should I do?&lt;/p&gt;
&lt;h1 id=&quot;set-up-router-forwarding&quot;&gt;Set up router forwarding&lt;/h1&gt;
&lt;p&gt;If your router supports default gateway switching, you can do this. (Most routers support this)&lt;/p&gt;
&lt;p&gt;Go to the router administration page (remember it?) and click on DHCP settings. Under the Default Gateway IP address, input the VM’s IP.&lt;/p&gt;
&lt;p&gt;Now reboot the router. If it works, great!&lt;/p&gt;
&lt;p&gt;In odd cases, the default gateway change might actually just change the administration page’s IP address. If that happens, there will be an address collision, and your VM box will either lose the IP address or lose access to the local network. In severe cases your router administration page might not come up. In that case, just disconnect the VM box, reboot the router and connect to the administration page to change the default gateway back. If this is the case, then your router does not support default gateway switching.&lt;/p&gt;
&lt;h1 id=&quot;connection-helping-script&quot;&gt;Connection-helping script&lt;/h1&gt;
&lt;p&gt;Now, since China is great for these TCP RST packets that drop your VPN connection sporadically (so you have to remote into your VM and fix it), &lt;a href=&quot;https://github.com/ericswpark/python_vpn-client&quot; rel=&quot;noopener noreferrer nofollow&quot; target=&quot;_blank&quot;&gt;I wrote a Python script that helps me out with the entire process.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Any improvements in the form of pull requests would be welcome!&lt;/p&gt;</content:encoded></item><item><title>Use NSSavePanel in Swift</title><link>https://ericswpark.com/blog/2018/2018-02-28-use-nssavepanel-in-swift/</link><guid isPermaLink="true">https://ericswpark.com/blog/2018/2018-02-28-use-nssavepanel-in-swift/</guid><pubDate>Wed, 28 Feb 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;TL;DR:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;@IBAction&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; func&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; browseDirectory&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sender: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Any&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; dialog &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; NSSavePanel&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    dialog.title &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Save file&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    dialog.showsResizeIndicator &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    dialog.canCreateDirectories &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    dialog.showsHiddenFiles &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    dialog.allowedFileTypes &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;txt&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (dialog.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;runModal&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; NSApplication.ModalResponse.OK) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; result &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; dialog.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;url&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (result &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; path &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; result&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.path &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Do stuff with &apos;path&apos; variable&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    } &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; // User clicked cancel&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Don’t forget to allow the app to access the filesystem.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Okay, we need to make a dialog, so let’s make a variable named dialog and assign it the NSSavePanel.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; dialog &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; NSSavePanel&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we have to set some parameters. Let’s give the dialog a title-bar name.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;dialog.title &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Save file&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The parameter names are pretty self-explanatory, but these options are for showing the “resize” button in the title bar, allowing users to create new folders, and showing hidden files.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;dialog.showsResizeIndicator &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;dialog.canCreateDirectories &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;dialog.showsHiddenFiles &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you want to restrict users so that they can only select certain files:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;dialog.allowedFileTypes &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;php&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We then expose the dialog to the users:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (dialog.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;runModal&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; NSApplication.ModalResponse.OK) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; result &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; dialog.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;url&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (result &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        let&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; path &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; result&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.path &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Do stuff with &apos;path&apos; variable&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; // User clicked cancel&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Great, so we’re done now, right? Not so fast! If you run your app now, it will crash. What gives? Well, you forgot to allow the app access to your filesystem! Simply click on your project on the left sidebar, press on your app under the smaller sidebar with the title-name “Targets”, choose “Capabilities” on the top bar, and then choose Read/Write under “User Selected File”.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;xcode-nssavepanel-permissions&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1003&quot; height=&quot;474&quot; src=&quot;/_astro/xcode-nssavepanel-permissions.DKs08D04_Z2d6Rye.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;You should now have a functional save panel!&lt;/p&gt;</content:encoded></item></channel></rss>