<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Rafael Cavalcanti - rafaelc.org</title><description>Updates from rafaelc.org</description><link>https://rafaelc.org/</link><language>en-us</language><item><title>Running a web server while using Pi Hole</title><link>https://rafaelc.org/posts/running-a-web-server-while-using-pi-hole</link><guid isPermaLink="true">https://rafaelc.org/posts/running-a-web-server-while-using-pi-hole</guid><pubDate>Wed, 18 Jul 2018 18:27:38 GMT</pubDate><content:encoded>&lt;p&gt;If you are running Pi Hole and want to deploy a web application to your Raspberry Pi, chances are that you’ll hit your head to the wall. That’s because Pi Hole installs and configures a web server (Lighttpd) for itself using port 80.&lt;/p&gt;
&lt;p&gt;This way, Lighttpd:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Serves the admin dashboard at &lt;code&gt;http://pi.hole/&lt;/code&gt; and &lt;code&gt;http://THE.PI.IP/admin&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Shows a &quot;blocked&quot; page whenever you bump at a blocked domain.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;So what can you do?&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;&lt;a href=&quot;/posts/web-server-and-pi-hole-a-how-to&quot;&gt;(If you just want a step by step solution, you can cut through the talk and jump to part 2.)&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;There are many solutions to this, and, of course, which one suits you best depends on your specific situation. On this post, I’ll list some of them, their caveats, and explain what I did on my Pi.&lt;/p&gt;
&lt;p&gt;First is the one I dislike more: &lt;strong&gt;buying another Raspberry Pi&lt;/strong&gt;. Alright, that’s a no-brainer if those apps will be internet facing, but for LAN… Just why? Hell, this device can handle a lot more than Pi Hole. There’s no need to put more pressure to the environment and to our pockets.&lt;/p&gt;
&lt;p&gt;Another solution is to &lt;strong&gt;bind your web server to a port other than 80&lt;/strong&gt;. This can be fine for some situations, but in my case it doesn&apos;t make it. My Pi serves a bunch of web applications for the LAN. Instead of forcing the users to memorize different ports, or make them go through a menu, I assign friendly domain names. This intended ease of use on my LAN makes port 80 fundamental for me.&lt;/p&gt;
&lt;p&gt;So going forward, you could &lt;strong&gt;edit the Lighttpd configuration&lt;/strong&gt; set by Pi Hole. This way, you would make Lighttpd also serve your applications, for example choosing them according to the name on the request.&lt;/p&gt;
&lt;p&gt;This one could sure do it, but it isn&apos;t a very clean approach. Why? You have to learn a bit of what Pi Hole expects the web server to do and not mess it up. Also, a possible change upstream will have to be merged into your configuration.&lt;/p&gt;
&lt;p&gt;Another problem with this solution: what if you don’t want to use Lighttpd for your projects? Maybe you have a very complex Nginx or Apache setup and don’t see yourself rewriting it because of Pi Hole.&lt;/p&gt;
&lt;p&gt;In this case, an alternative is to nuke the Lighttpd installation, &lt;strong&gt;install the web server of your choice and then proceed to do all the configuration&lt;/strong&gt; – for your apps, and for Pi Hole. However, this solution has the same caveat of having to mess with the web configuration of Pi Hole.&lt;/p&gt;
&lt;h2&gt;Alright, so what did I come with for my Pi?&lt;/h2&gt;
&lt;p&gt;I decided to take a simpler and cleaner approach. Besides being easy to setup, using it for 20 months resulted in no headaches. This is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;set an additional IP for the Raspberry Pi&lt;/li&gt;
&lt;li&gt;bind Lighttpd to the original IP&lt;/li&gt;
&lt;li&gt;bind our app web server to the new IP&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And we are done. This way you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Can use port 80 however you want, all for you, as if Pi Hole wasn&apos;t installed.&lt;/li&gt;
&lt;li&gt;Use whatever web server you pick for your apps, configuring it as you always did.&lt;/li&gt;
&lt;li&gt;Barely have to touch whatever Pi Hole configured Lighttpd to do, so it works exactly as intended.&lt;/li&gt;
&lt;li&gt;Don&apos;t have to perform any maintenance since Pi Hole 4.2.2 &lt;em&gt;(on earlier versions you only had to do a minor edit on configuration at every update)&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The caveat here is that you have to run two separate web servers. Maybe for your situation this won&apos;t be acceptable. But in my particular setup and typical load, there&apos;s no performance impact in any significant way.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/posts/web-server-and-pi-hole-a-how-to&quot;&gt;The next post covers those steps in detail.&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>The motivation for PiFi Radio</title><link>https://rafaelc.org/posts/the-motivation-for-pifi-radio</link><guid isPermaLink="true">https://rafaelc.org/posts/the-motivation-for-pifi-radio</guid><pubDate>Sat, 11 Aug 2018 01:36:17 GMT</pubDate><content:encoded>&lt;p&gt;PiFi Radio is a MPD web client focused on playing radio streamings. &lt;a href=&quot;https://rafaelc.org/pifi&quot;&gt;As I recently open sourced it&lt;/a&gt;, I&apos;ll try to explain what made me write it.&lt;/p&gt;
&lt;h2&gt;The need&lt;/h2&gt;
&lt;p&gt;I started this project in early 2017. At that time, I wanted to setup a Raspberry Pi for my parents to listen to internet radio and control it with ease.&lt;/p&gt;
&lt;p&gt;That Pi also served other purposes, so installing an audio dedicated distro, such as Volumio, wasn&apos;t an option. I installed MPD on top of Raspbian and looked for a client. I needed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A straight-forward user interface that wouldn&apos;t confuse them. Few taps to the desired actions and little room to mistakes.&lt;/li&gt;
&lt;li&gt;The interface should, at least, present a list of stations from which to pick, and allow to control the volume and stop the current playback.&lt;/li&gt;
&lt;li&gt;The administrator should have the ability to set a list of streams and make it available to all users.&lt;/li&gt;
&lt;li&gt;The client should be usable at least from iOS and Android phones, but ideally everywhere.&lt;/li&gt;
&lt;li&gt;It should be in their native language.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What I found at that time was the following.&lt;/p&gt;
&lt;h2&gt;Available MPD clients at the time&lt;/h2&gt;
&lt;p&gt;There were many great MPD clients available on Android, desktop and web. The &quot;forgotten&quot; platform was iOS, for which there was only one (paid) app on the AppStore. This was not a big of an issue, as I could still set up a web client for those devices.&lt;/p&gt;
&lt;p&gt;However, I couldn&apos;t find a client that treated radios with much care. The majority of them privileged the music library, with streaming as a hacky, second class citizen &lt;em&gt;(as you’ll see PiFi is the opposite)&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;The usual experience for radio listening&lt;/h2&gt;
&lt;p&gt;On most clients, to have a list of radio streams you need to create a playlist on the server and add the stations to it. Then, to choose a radio, the user has to find that playlist on the app and tap the desired streaming. This has many problems for me.&lt;/p&gt;
&lt;p&gt;First of all, equating a list of radios to a playlist can be confusing to some users. Also, it has some unexpected behaviours. For example, if the user selects a station with a broken URL, MPD will start to play the next radio. So the user is confused: he asked for one station, but other one is playing. He will probably tap again and won&apos;t understand why this continues to happen.&lt;/p&gt;
&lt;p&gt;Other problem is that you usually can’t set a friendly name to the stream. The playlist is presented to the user as a series of URLs or some badly formatted info that the station broadcasted. So, when choosing the radio playlist, the user is presented a list consisting of (possibly giant and frightening) URLs, random song names and, when he is lucky, ugly formatted station names. The same lack of clarity happens when the user open the app to check what station is currently playing.&lt;/p&gt;
&lt;p&gt;I also didn&apos;t like that the playlists are editable by the users. This makes sense for song playlists, but I don&apos;t expect them to mess with radio streaming URLs. I wanted this to be reserved to the backend.&lt;/p&gt;
&lt;p&gt;One more issue is that, since those clients are focused on the library, someone who uses them just to play radio finds the interface far from straight-forward, actually rather confusing. Many MPD clients are packed with features related to the music library. The result is the screen filled with options that radio listeners don’t care about. This could be very frustrating to them, specially because the same clients lack basic behaviours that you would expect from a radio app. My parents would actually have to memorize steps just to play a station. Not good.&lt;/p&gt;
&lt;p&gt;The most promising contenders were some desktop applications. A couple of them had radio lists conveniently fetched from web services such as Soma FM, TuneIn, Shoutcast, etc, and even some sort of local radio bookmarks. However, I wanted the same &quot;favourites&quot; list to be available across all users and devices, and those apps still had the issue of confusing names for the playing stations. Worst of all, desktop wasn&apos;t my priority. So this couldn&apos;t do it.&lt;/p&gt;
&lt;p&gt;Finally, few applications were translated to the language I needed. This could be remediable, but at this point it didn&apos;t matter anymore.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The bottom line: while there are tons of great MPD clients, you will hardly have a nice time using software for something it wasn&apos;t designed. Most clients are focused on the MPD music library, and at that time I couldn&apos;t find any that took streaming with care.&lt;/p&gt;
&lt;p&gt;It was then obvious to me that I should create my own solution to my problem. I decided to code a simple web app and call it PiFi Radio, a MPD web client for listening to radio. &lt;a href=&quot;https://rafaelc.org/pifi&quot;&gt;You can see how it went on my Github.&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>A cause for error 0x80070057 on Windows 10</title><link>https://rafaelc.org/posts/a-cause-for-error-0x80070057-on-windows-10</link><guid isPermaLink="true">https://rafaelc.org/posts/a-cause-for-error-0x80070057-on-windows-10</guid><pubDate>Wed, 21 Nov 2018 16:45:15 GMT</pubDate><content:encoded>&lt;p&gt;After years as a full-time Linux user, lately I decided to fiddle a bit more with Windows 10.&lt;/p&gt;
&lt;p&gt;Just a couple of days passed from a clean Windows installation, and I faced a somewhat obscure bug. The solution I found might be helpful to someone else.&lt;/p&gt;
&lt;p&gt;The issue was these operations would fail most of the time:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Installing or updating software via Windows Store&lt;/li&gt;
&lt;li&gt;Installing Windows updates&lt;/li&gt;
&lt;li&gt;Windows installing &quot;new resources&quot;&lt;/li&gt;
&lt;li&gt;Updating Apple software&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The error displayed was 0x80070057.&lt;/p&gt;
&lt;p&gt;If you Google it, you&apos;ll see this error hasn&apos;t a single known cause, and attempting to fix it consists on trying multiple things, including nuking your whole Windows installation &lt;em&gt;(and after reinstallation the error might still be there :-D)&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;In this &lt;em&gt;beautiful&lt;/em&gt; scenario, I went to do some troubleshooting. I detected that, in my system, that error was related to a specific software I had installed and running: EncFSMP 0.11.1.&lt;/p&gt;
&lt;p&gt;Simply unmonting all drives in this application makes the error go away.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/rhiestan/EncFSMP/issues/12&quot;&gt;I&apos;ve filled a bug for it.&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>MIUI Notes trims text without warning</title><link>https://rafaelc.org/posts/miui-notes-trims-text-without-warning</link><guid isPermaLink="true">https://rafaelc.org/posts/miui-notes-trims-text-without-warning</guid><pubDate>Tue, 30 Apr 2019 02:54:03 GMT</pubDate><content:encoded>&lt;p&gt;This entry is just a friendly warning if you happen to use the Notes app from Xiaomi.&lt;/p&gt;
&lt;p&gt;These days I shared a few texts to it via Android&apos;s share menu. All seemed well. No error message and all texts were there as new notes, as expected.&lt;/p&gt;
&lt;p&gt;However, later when I opened them, I realized that some ended abruptly. They were trimmed around 16,300 characters (which probably not by accident is close to 2&lt;sup&gt;14&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;In order to know which app was to blame, I repeated the sharing but to other app instead of Notes. The result then was fine: the text was fully there.&lt;/p&gt;
&lt;p&gt;So, this seems like MIUI Notes not taking proper action when receiving texts that exceed its character limit. Not even a warning to the user.&lt;/p&gt;
&lt;p&gt;I filled a bug to Xiaomi, but while it is not fixed, be aware so this doesn&apos;t get you in trouble.&lt;/p&gt;
&lt;p&gt;For the record, the MIUI Notes version is 1.5.2.&lt;/p&gt;
</content:encoded></item><item><title>An anti-theft strategy on Xiaomi phones</title><link>https://rafaelc.org/posts/an-anti-theft-strategy-on-xiaomi-phones</link><guid isPermaLink="true">https://rafaelc.org/posts/an-anti-theft-strategy-on-xiaomi-phones</guid><pubDate>Mon, 08 Jul 2019 02:00:21 GMT</pubDate><content:encoded>&lt;p&gt;In this post, I want to share an idea that I had last year and may help secure your data on Xiaomi phones.&lt;/p&gt;
&lt;h2&gt;The scenario&lt;/h2&gt;
&lt;p&gt;You may be faced with a situation in which someone, normally a thief, constrain you to give away your PIN. Needless to say, together your PIN, he&apos;ll have access to most of what you have on your phone.&lt;/p&gt;
&lt;h2&gt;A first approach&lt;/h2&gt;
&lt;p&gt;To protect against that kind of adversary, a first approach would be to add a second layer of protection to your most important apps and data.&lt;/p&gt;
&lt;p&gt;For example, for apps you can use MIUI&apos;s App Lock. This asks for your fingerprint or face unlock before opening some apps. For data, you could use MIUI&apos;s password protection for specific folders.&lt;/p&gt;
&lt;p&gt;However, that approach can quickly get out of hand, and make a pain to user your own phone. The strategy I will share protects your data further, while not impacting a lot on your daily phone use.&lt;/p&gt;
&lt;h2&gt;My solution&lt;/h2&gt;
&lt;p&gt;So you might know that MIUI has a feature called Second Space. That&apos;s basically Android letting you create a second user for your device.&lt;/p&gt;
&lt;p&gt;However, Xiaomi&apos;s implementation has an interesting feature that is absent on AOSP - and I guess for many manufacturers. Whenever you unlock your phone, MIUI can take you to the first or to the second space depending on which PIN you type. Cool, uh?&lt;/p&gt;
&lt;p&gt;On AOSP (at least until Android 9), you need to unlock the current user in order to switch to another one, through the notification shade.&lt;/p&gt;
&lt;p&gt;Knowing that MIUI feature, we can use it to further protect our personal data.&lt;/p&gt;
&lt;h2&gt;Hands on&lt;/h2&gt;
&lt;p&gt;So let&apos;s set it.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Open the Settings. The Second Space configuration is placed in a different place depending on your MIUI version. On MIUI 11, it is under &lt;em&gt;Special features -&amp;gt; Second Space&lt;/em&gt;. If you can&apos;t see it, try searching for &lt;em&gt;&quot;Second Space&quot;&lt;/em&gt; on the search bar.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Tap &lt;em&gt;&quot;Turn on Second Space&quot;&lt;/em&gt;, wait for the completion, and then press &lt;em&gt;&quot;Continue&quot;&lt;/em&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A screen will show up asking if you want to switch between the spaces using a password or using a shortcut. &lt;strong&gt;Press &quot;using a password&quot;.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You&apos;ll be asked for your first space password. Enter it, and you&apos;ll be asked to set the password for the second space. Obviously, use a different one.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You can skip setting a fingerprint.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Good. By now you should have landed on the Second Space. We&apos;ll further hide the fact that this isn&apos;t the main space, doing the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Go to &lt;em&gt;Settings -&amp;gt; Special Features -&amp;gt; Second Space&lt;/em&gt; (or wherever it is placed on your MIUI version).&lt;/li&gt;
&lt;li&gt;Under &lt;em&gt;&quot;Second Space Settings&quot;&lt;/em&gt; disable both options: &lt;em&gt;&quot;Home screen shortcut&quot;&lt;/em&gt; and &lt;em&gt;&quot;Show notifications from First Space&quot;&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Under &lt;em&gt;&quot;Other settings&quot;&lt;/em&gt; disable &lt;em&gt;&quot;Second Space icon&quot;&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And we are done.&lt;/p&gt;
&lt;p&gt;Now you go on with your life, and just don&apos;t place any personal data in the Second Space.&lt;/p&gt;
&lt;p&gt;If, at any time, someone constrains you for your PIN, just give the secondary one. The phone will unlock, making them probably happy, but without any access to your data and apps. Isn&apos;t it nice?&lt;/p&gt;
&lt;h2&gt;Caveats&lt;/h2&gt;
&lt;p&gt;So far, the only caveat I&apos;ve experienced is the following. If you have been using your phone normally (in the First Space) and then coldly enter the secondary pin, the phone will take a longer time to unlock.&lt;/p&gt;
&lt;p&gt;I hope this helps someone.&lt;/p&gt;
</content:encoded></item><item><title>Digital reading, highlights and notes</title><link>https://rafaelc.org/posts/digital-reading-highlights-and-notes</link><guid isPermaLink="true">https://rafaelc.org/posts/digital-reading-highlights-and-notes</guid><pubDate>Thu, 19 Sep 2019 16:39:51 GMT</pubDate><content:encoded>&lt;p&gt;When reading, I consider the ability of highlighting and taking notes a basic requirement. If I can&apos;t do it in some way, even with &lt;em&gt;rudimentary&lt;/em&gt; copy and past, my reading experience becomes much less rich.&lt;/p&gt;
&lt;h2&gt;The possibilities&lt;/h2&gt;
&lt;p&gt;The arrival of digital reading should help us with annotations, improving them and opening new possibilities.&lt;/p&gt;
&lt;p&gt;Some of the obvious improvements over analog include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No need to be afraid to mess a book or document.&lt;/li&gt;
&lt;li&gt;Write without being conscious about the length of the note or trying to fit a small blank space at the side of the page with tiny letters.&lt;/li&gt;
&lt;li&gt;Easily and quickly delete the annotations, without damaging the pages.&lt;/li&gt;
&lt;li&gt;Browse and search through all your annotations fast.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Besides that, we should also explore new ideas and possibilities, instead of just mirroring what we used to do in paper. On the other hand, obviously, not everything is progress: some analog features are difficult to replicate.&lt;/p&gt;
&lt;h2&gt;The actual state&lt;/h2&gt;
&lt;p&gt;However, the actual state of it always bothered me. After all these years, we still lack a standard way of handling annotations. They work in different ways depending on the file format, the platform you are using and other details. This causes many problems, such as:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Your annotations can easily get spread over all kinds of services, apps, etc.&lt;/li&gt;
&lt;li&gt;Interoperability is nearly nonexistent. I can&apos;t browse the annotations I have in different platforms in a central place.&lt;/li&gt;
&lt;li&gt;They can get vendor locked.&lt;/li&gt;
&lt;li&gt;They are stored (and exported) in many different formats.&lt;/li&gt;
&lt;li&gt;Importing annotations from one place to other varies from difficult to virtually impossible.&lt;/li&gt;
&lt;li&gt;Good luck if you ever want &lt;em&gt;(or need)&lt;/em&gt; to change software while reading a ton of books... You&apos;ll probably need to stick to both and go back and forward to see your annotations spread over them.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;All that is inelegant and inefficient. We still get the benefits I listed above, but in a limited and messy way. Worse yet, we barely advanced in exploring new ideas. Consequently, we are far from taking advantage from all the possibilities the digital opened to us.&lt;/p&gt;
&lt;h2&gt;Storing and exporting&lt;/h2&gt;
&lt;p&gt;Let&apos;s see some examples of how things work currently.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;PDFs: If you are reading PDFs, many software support that you annotate them directly on the file.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ePubs: In the case of ePubs, each reader stores the annotations internally. Some of them do allow exporting, with a varying degree of convenience. But to which format? Well, it also depends on the app. It can be a fancy &lt;code&gt;docx&lt;/code&gt;, a messy &lt;code&gt;txt&lt;/code&gt;, a well-formated &lt;code&gt;markdown&lt;/code&gt;, etc.&lt;/p&gt;
&lt;p&gt;In particular, Google Play Books stores your annotations with their copy of the book, and allows you to export it to a Google Docs file, which also gets automatically updated. &lt;em&gt;(IMO one of the best solutions out there, yet far from ideal.)&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;DRM&apos;ed books: For DRM&apos;ed books, such as the ones you buy on Amazon, the situation is similar to what I just described for ePubs. With the glorious exception that you are stuck using their reader apps.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Kindle devices: About the Kindle, specifically, &lt;a href=&quot;/posts/export-all-your-kindle-highlights-and-notes&quot;&gt;I talked on this post&lt;/a&gt;. &lt;em&gt;TL;DR: also a mess.&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;A possible solution and more problems&lt;/h2&gt;
&lt;p&gt;We just saw that many software have some way to export your annotations. So we could think of a solution as the following:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;It is just a matter of using applications that support exporting to a nice format. Use them to read the book and annotate it. Export the annotations when finishing the book. Store all them in a same folder.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is more or less my current strategy &lt;em&gt;(which I will detail next)&lt;/em&gt;, but it is still far from ideal.&lt;/p&gt;
&lt;p&gt;For example, what if you want to see your annotation &lt;em&gt;IN&lt;/em&gt; the book? Let&apos;s say I want to build my digital library, and all my books would have my annotations, like in real life. How would a separate folder with tons of annotations would achieve that?&lt;/p&gt;
&lt;p&gt;Worse than that. As I said, e-books annotations should give us more features, such as browsing the book by them, maybe searching them by genre, etc. None of those is given by a &lt;code&gt;html&lt;/code&gt; or &lt;code&gt;docx&lt;/code&gt; file with my highlights.&lt;/p&gt;
&lt;h2&gt;A second solution: books and annotations together&lt;/h2&gt;
&lt;p&gt;So what can we do to have a personal library with our digitally annotated books?&lt;/p&gt;
&lt;p&gt;Well, since each app has their own internal method of storing the annotations, you&apos;ll have to pretty much stick with a particular software all your life. This has, at least, 4 problems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;First, the most obvious one.&lt;/p&gt;
&lt;p&gt;You may want to switch to other software in the future, with features you consider useful.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You have to count to the software to be operational until your death.&lt;/p&gt;
&lt;p&gt;For example, you may decide to use Play Books, and add all your books to there, &lt;em&gt;&lt;em&gt;but...&lt;/em&gt;&lt;/em&gt; If you ever stop using it or if it is discontinued, all you&apos;ll have are your Google documents. Your annotated library is gone. You cannot browse your ePub anymore with your annotations.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Migration is a pain.&lt;/p&gt;
&lt;p&gt;Let&apos;s say, for the sake of the argument, that you found that &lt;em&gt;&lt;em&gt;perfect&lt;/em&gt;&lt;/em&gt; software, that you&apos;ll use forever, and it will &lt;em&gt;&lt;em&gt;surely&lt;/em&gt;&lt;/em&gt; never drop support or stop working. OK, let&apos;s just use it forever and all is solved, right? Not quite. How would it import the annotations for all the books you&apos;ve read until today?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You may buy a different e-reader device.&lt;/p&gt;
&lt;p&gt;If you buy a new e-reader you&apos;ll probably not be able to import the annotations from this software. Bye-bye, library.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So no luck.&lt;/p&gt;
&lt;h2&gt;My strategy&lt;/h2&gt;
&lt;p&gt;If you read until here, you&apos;re probably thinking &lt;em&gt;&quot;what a mess&quot;&lt;/em&gt;! And I agree.&lt;/p&gt;
&lt;p&gt;I&apos;ll try to shine some light with my current strategy to keep my digital annotations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pick an app/platform/e-reader and try to stick the most to it. &lt;em&gt;E.g.: your Kindle, Google Play Books, etc.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Make sure to find a way to periodically export your annotations from it to a universal format, such as plaintext, PDF, etc.&lt;/li&gt;
&lt;li&gt;Also convert the readings you do out of that platform to a universal format.&lt;/li&gt;
&lt;li&gt;Keep everything stored and organized in a directory or database.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This aims to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Have all your annotations in one place.&lt;/li&gt;
&lt;li&gt;Have most of your content annotated (on the platform you pick).&lt;/li&gt;
&lt;li&gt;Be safe against your annotations being vendor locked.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A big part of how you implement that strategy and how well it will work depends on your habits, and which formats and platforms you use. Here is one example of it in action:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Chosen platform: Google Play Books.&lt;/li&gt;
&lt;li&gt;Do most of your reading there on Google Play Books.&lt;/li&gt;
&lt;li&gt;Enable the built-in sync of annotations to Google Docs.&lt;/li&gt;
&lt;li&gt;For your readings outside Play Books, save your annotations in markdown format and store in the same Google Drive folder.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&apos;s a shame that, in 2019, the best strategy I developed for myself &lt;em&gt;still&lt;/em&gt; has some important compromises. For the future, I hope we finally get a standardized way of doing all that.&lt;/p&gt;
&lt;p&gt;Btw, if you happen to use a Kindle, &lt;a href=&quot;/posts/export-all-your-kindle-highlights-and-notes&quot;&gt;this post might interest you&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Mounting KDE Connect filesystem via CLI</title><link>https://rafaelc.org/posts/mounting-kde-connect-filesystem-via-cli</link><guid isPermaLink="true">https://rafaelc.org/posts/mounting-kde-connect-filesystem-via-cli</guid><pubDate>Sat, 03 Aug 2019 22:15:12 GMT</pubDate><content:encoded>&lt;p&gt;One of the multiple useful features from KDE Connect is browsing your phone files on your desktop. However, that&apos;s currently only available through the GUI, e.g., in KDE Connect tray icon or Dolphin&apos;s side panel. KDE Connect does have a command-line tool (&lt;code&gt;kdeconnect-cli&lt;/code&gt;), but it lacks that option.&lt;/p&gt;
&lt;p&gt;So if, for any reason, you need to do that mounting programatically, this guide should help you.&lt;/p&gt;
&lt;h2&gt;Getting what we need&lt;/h2&gt;
&lt;p&gt;First of all, let&apos;s gather the information we need. Mount the filesystem clicking KDE Connect&apos;s tray icon and selecting &lt;em&gt;Browse device&lt;/em&gt;. Now run &lt;code&gt;mount | grep kdeconnect&lt;/code&gt;, and you should get this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;kdeconnect@PHONE_IP:/ on /run/user/UID/PHONE_ID type fuse.sshfs (rw,nosuid,nodev,relatime,user_id=UID,group_id=GID)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This shows us that KDE Connect mounts the exposed filesystem through SFTP, connecting to a SSH server on our phone as the user &lt;code&gt;kdeconnect&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So now we should find to which port it connects on our phone. With &lt;code&gt;netstat&lt;/code&gt;, I found it to be the port 1740. Please check if your port is the same.&lt;/p&gt;
&lt;p&gt;Finally, we should find KDE Connect private key to attempt the connection. This should be at &lt;code&gt;~/.config/kdeconnect/privateKey.pem&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Mounting the filesystem&lt;/h2&gt;
&lt;p&gt;With all that information, let&apos;s now try to mount our phone filesystem with &lt;code&gt;sshfs&lt;/code&gt;. Replace &lt;code&gt;~/mnt&lt;/code&gt; with whatever mount-point you want, and &lt;code&gt;PHONE_IP&lt;/code&gt; to your actual phone IP, and issue:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sshfs -o rw,nosuid,nodev,identityfile=$HOME/.config/kdeconnect/privateKey.pem,port=1740 kdeconnect@PHONE_IP:/ ~/mnt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If it went well, you&apos;re done. Now you can use that command to easily mount your phone filesystem via CLI.&lt;/p&gt;
</content:encoded></item><item><title>A way to organize your Bash aliases on multiple hosts</title><link>https://rafaelc.org/posts/a-way-to-organize-your-bash-aliases-on-multiple-hosts</link><guid isPermaLink="true">https://rafaelc.org/posts/a-way-to-organize-your-bash-aliases-on-multiple-hosts</guid><pubDate>Mon, 27 Aug 2018 14:34:25 GMT</pubDate><content:encoded>&lt;p&gt;If you use multiple systems, you probably have a method of syncing your dotfiles between them. But different systems also have different needs.&lt;/p&gt;
&lt;p&gt;When we talk about Bash, not properly managing those differences could mean having tons of useless aliases polluting your environment, just because they are wanted on other machines. Or a very cluttered &lt;code&gt;~/.bash_aliases&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;On this post, I&apos;ll talk about my approach to handle different Bash aliases on multiple machines.&lt;/p&gt;
&lt;h2&gt;Some solutions&lt;/h2&gt;
&lt;p&gt;A first try to handle the differences between hosts is, obviously, throwing some conditionals to your &lt;code&gt;~/.bash_aliases&lt;/code&gt;. For example, if you have aliases to &lt;code&gt;cd&lt;/code&gt; to important directories, you could condition them to the existence of the folder. I do this to some extent and helps to keep the environment clean.&lt;/p&gt;
&lt;p&gt;However, it only works to a certain point. When you deal with systems that serve totally different purposes, say a Raspberry Pi server vs. your laptop, this method becomes cumbersome. Your &lt;code&gt;~/.bash_aliases&lt;/code&gt; will be filled with conditionals, some maybe with difficult logic, and badly organized. Not to mention giant.&lt;/p&gt;
&lt;p&gt;An alternative to this is to have multiple copies of your aliases file with the needed differences, and point each machine to the correspondent copy. The advantage here is that you have more readable files, but in the other hand, tons of duplication and management hell. Not nice either.&lt;/p&gt;
&lt;p&gt;So, let&apos;s talk about my approach.&lt;/p&gt;
&lt;h2&gt;My approach&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;# ~/.bashrc

# Alias definitions.
if [ -f ~/.config/bash_aliases/$HOSTNAME ]; then
  . ~/.config/bash_aliases/$HOSTNAME
else
  . ~/.config/bash_aliases/default
fi
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;~/.config/bash_aliases
├── base        # Common aliases
├── default     # Aliases for generic hosts
├── foo         # Aliases for host foo
└── bar         # Aliases for host bar
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First of all, we create a directory to store the aliases.&lt;/p&gt;
&lt;p&gt;On &lt;code&gt;~/.bashrc&lt;/code&gt;, we search that path for a file named as the current hostname. If it doesn&apos;t exist, we source &lt;code&gt;default&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We also create a file called &lt;code&gt;base&lt;/code&gt;, which may be sourced by each of the other files.&lt;/p&gt;
&lt;p&gt;Now let&apos;s dive into that directory.&lt;/p&gt;
&lt;h2&gt;The base file&lt;/h2&gt;
&lt;p&gt;In &lt;code&gt;base&lt;/code&gt;, you place the aliases that you want to be available for every host.&lt;/p&gt;
&lt;p&gt;In my case, this include things as simple as &lt;code&gt;alias ll=&quot;ls -l&quot;&lt;/code&gt; to more complex ones, and it is my biggest file.&lt;/p&gt;
&lt;p&gt;I don&apos;t limit this file to which software I have available on all machines. My mindset here is to include everything that doesn&apos;t target any system specifically.&lt;/p&gt;
&lt;p&gt;For example, mine has an alias &lt;em&gt;(actually a function)&lt;/em&gt; to easily call snapper, although I don&apos;t have it installed on all my machines. It is there because, if someday I install snapper on whatever system, I&apos;ll probably want that alias.&lt;/p&gt;
&lt;p&gt;So if something depends on the presence of a software, and not on which host I am, it probably belongs here on &lt;code&gt;base&lt;/code&gt;, conditioned to the existence of the binaries on the system. Easy enough.&lt;/p&gt;
&lt;h2&gt;The host-specific files&lt;/h2&gt;
&lt;p&gt;The next step is to create a file for each system needing their own aliases. This file is named after their hostname.&lt;/p&gt;
&lt;p&gt;Here you source &lt;code&gt;base&lt;/code&gt;, and then set the additional aliases.&lt;/p&gt;
&lt;p&gt;Other thing you might do here is to override some of the &lt;code&gt;base&lt;/code&gt; aliases. &lt;em&gt;&quot;Why would I do that?&quot;&lt;/em&gt; You might ask.&lt;/p&gt;
&lt;p&gt;A example is if one of your systems uses Busybox instead of Coreutils. The former is a smaller version of the tools we are used to in Linux, and, as such, often has different (and less) command-line options. This breaks some of my aliases, such as &lt;code&gt;rm -vI&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I solve it by overriding those aliases in my host-specific file for a &quot;good enough&quot; version of them. I think this makes more sense than removing the affected aliases from &lt;code&gt;base&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Back to your host-specific files, they will be something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# foo
# Aliases for a host called foo

# Source base
. ~/.config/bash_aliases/base

# Specific and overriding aliases for foo
...
...

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;The default file&lt;/h3&gt;
&lt;p&gt;Lastly, for the systems in which you don&apos;t need specific configuration, you have a fallback file called &lt;code&gt;default&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here you do the same thing as in the host-specific files, i.e., source &lt;code&gt;base&lt;/code&gt; and then set or override aliases. The difference is that you are targeting the devices that won&apos;t match a specific hostname.&lt;/p&gt;
&lt;p&gt;An alternative to write this file is to point those systems directly to &lt;code&gt;base&lt;/code&gt;. For me, though, it makes sense to have a layer on top of &lt;code&gt;base&lt;/code&gt;, to give me some flexibility.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I came with this approach to my aliases and it&apos;s serving me very well. I don&apos;t have duplicated code, my files are very readable and easily manageable. I also have a fair amount of flexibility.&lt;/p&gt;
&lt;p&gt;I can think of many ways this solution could be modified and extended to better suit other particular cases. Feel free to adapt it to your needs.&lt;/p&gt;
</content:encoded></item><item><title>Web server and Pi Hole - a how-to</title><link>https://rafaelc.org/posts/web-server-and-pi-hole-a-how-to</link><guid isPermaLink="true">https://rafaelc.org/posts/web-server-and-pi-hole-a-how-to</guid><pubDate>Thu, 19 Jul 2018 16:39:23 GMT</pubDate><content:encoded>&lt;p&gt;In the previous post, we discussed &lt;a href=&quot;/posts/running-a-web-server-while-using-pi-hole&quot;&gt;how I set up a web server alongside Pi Hole&lt;/a&gt;. Now, I will guide you step by step on how to do it on Raspbian Stretch.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;A word of caution. I think I don&apos;t need to remember you, but I will: &lt;strong&gt;please make a copy of the configuration files before editing.&lt;/strong&gt; Also, be careful if doing this remotely, since we are changing the network configuration and a mistake could make you lose your connection to the Pi.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Step 1 – Set a secondary static IP&lt;/h2&gt;
&lt;p&gt;In this first step, we&apos;ll configure your Pi to have two static IP addresses, one for Pi Hole and other to be used by your web apps.&lt;/p&gt;
&lt;p&gt;There are many ways to achieve it, and just this step could deserve a guide by itself. The method I describe here has worked best to me so far. Feel free to do your own research and come up with other (possibly better) ways.&lt;/p&gt;
&lt;h3&gt;Primary static IP&lt;/h3&gt;
&lt;p&gt;Let&apos;s make sure &lt;code&gt;dhcpcd&lt;/code&gt; is instructed to use a static IP. Your &lt;code&gt;/etc/dhcpcd.conf&lt;/code&gt; should have something similar to this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# /etc/dhcpcd.conf

...

interface wlan0
static ip_address=10.0.0.10/24
static routers=10.0.0.1
static domain_name_servers=127.0.0.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Replace the values on each line for the appropriate ones in your system. The IP in this example is &lt;code&gt;10.0.0.10&lt;/code&gt;. Be sure to pick an address no other device is using on your network.&lt;/p&gt;
&lt;p&gt;The interface being configured is &lt;code&gt;wlan0&lt;/code&gt;. If your Pi is connected by Ethernet, change it for &lt;code&gt;eth0&lt;/code&gt;. We also set the DNS for &lt;code&gt;127.0.0.1&lt;/code&gt;, so the Pi uses the Pi Hole to resolve the names he needs. The gateway and the netmask should be set accordingly to your network.&lt;/p&gt;
&lt;p&gt;Restart &lt;code&gt;dhcpcd&lt;/code&gt; or reboot your system:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo systemctl daemon-reload
$ sudo systemctl restart networking
$ sudo systemctl restart dhcpcd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Issue &lt;code&gt;ip a&lt;/code&gt; and check if the Pi got your chosen IP.&lt;/p&gt;
&lt;h3&gt;Secondary static IP&lt;/h3&gt;
&lt;p&gt;Now let&apos;s set the secondary IP. In the current times, this &lt;em&gt;ideally&lt;/em&gt; should also be done on &lt;code&gt;dhcpcd.conf&lt;/code&gt;. However, making it work properly has proven harder than it should.&lt;/p&gt;
&lt;p&gt;My current approach is to bring up the second IP through &lt;code&gt;/etc/network/interfaces&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Place a file under &lt;code&gt;/etc/network/interfaces.d&lt;/code&gt;, and name it for example &lt;code&gt;secondary_ip&lt;/code&gt;. Add the following lines, replacing the values with the appropriate ones:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# /etc/network/interfaces.d/secondary_ip

auto wlan0
iface wlan0 inet static
    address 10.0.0.11
    netmask 255.255.255.0
    dns-nameservers 127.0.0.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example, I pick &lt;code&gt;10.0.0.11&lt;/code&gt; as the secondary IP.&lt;/p&gt;
&lt;p&gt;Have you done the proper configuration, restart your network or reboot. Your Pi should now have two static IPs. Let&apos;s check it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ip a
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If all went well, you&apos;ll see both IPs in two lines starting with &lt;code&gt;inet&lt;/code&gt;. For our example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;...
inet 10.0.0.10/24 brd 192.168.0.255 scope global wlan0
  valid_lft forever preferred_lft forever
inet 10.0.0.11/24 brd 192.168.0.255 scope global secondary wlan0
  valid_lft forever preferred_lft forever
...
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Step 2 - Bind Lighttpd to the primary IP&lt;/h2&gt;
&lt;p&gt;Now we&apos;re going to make Lighttpd bind only to the primary address. This will leave port 80 free for our webserver in the other address. This step is much more straight-forward.&lt;/p&gt;
&lt;p&gt;Until recently, we achieve that editing &lt;code&gt;/etc/lighttpd/lighttpd.conf&lt;/code&gt;. This had the inconvenience that whenever Pi Hole updated, the file would be overwritten and all the changes lost. So every month or so, we would have to revert the relevant lines.&lt;/p&gt;
&lt;p&gt;However, since Pi Hole 4.2.2 that problem is gone. Now, the Lighttpd config done by Pi Hole sources a separate file, allowing us to permanently store our modifications in it.&lt;/p&gt;
&lt;p&gt;This file is &lt;code&gt;/etc/lighttpd/external.conf&lt;/code&gt;. Add this line to it, replacing &lt;code&gt;10.0.0.10&lt;/code&gt; by the primary IP address you just set:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;server.bind = &quot;10.0.0.10&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If for whatever reason you are using an older version of Pi Hole, simply add that line to &lt;code&gt;lighttpd.conf&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Let&apos;s restart Lighttpd:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo systemctl restart lighttpd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the Pi Hole admin console should only be available on your primary IP.&lt;/p&gt;
&lt;h2&gt;Step 3 - Bind your web server to the secondary IP&lt;/h2&gt;
&lt;p&gt;Finally, we need to make your web server bind only to the secondary IP. The procedure is analogous to what we did on step 2: edit the configuration of your server, binding it to your Pi&apos;s secondary IP.&lt;/p&gt;
&lt;p&gt;The specifics on how to do this depend on which web server you pick and your configuration.&lt;/p&gt;
&lt;p&gt;For example, if you are using Nginx, set the &lt;code&gt;listen&lt;/code&gt; directive on your site configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# /etc/nginx/sites-available/default
# (or whatever the path for your site configuration)

...
listen 10.0.0.11:80
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Please refer to the documentation of your web server for details.&lt;/p&gt;
&lt;p&gt;After making this change, start the server and everything should work as we wanted.&lt;/p&gt;
&lt;h2&gt;Step 4 - Install Pi Hole or reconfigure it&lt;/h2&gt;
&lt;p&gt;If you haven&apos;t installed Pi Hole yet, do it now, pointing it to the primary IP.&lt;/p&gt;
&lt;p&gt;Alternatively, if Pi Hole is already installed, reconfigure it to make sure it points to that address. Run &lt;code&gt;pihole -r&lt;/code&gt; and select &quot;Reconfigure&quot;.&lt;/p&gt;
&lt;p&gt;This step is necessary because it tells Pi Hole to resolve the blocked domains to that address. It expects Lighttpd to be there to serve the &quot;Pi Hole blocked&quot; page.&lt;/p&gt;
&lt;p&gt;Also notice that this guide placed Pi Hole and Lighttpd in the primary IP and your webserver in the secondary, but the opposite is obviously also possible. The key is that Pi Hole and Lighttpd should be configured to the same address.&lt;/p&gt;
&lt;p&gt;As a side note, the DNS server binds to both IPs and we&apos;ll not touch it. We are only concerned about port 80, so this fact doesn&apos;t bring any issues and is irrelevant for our goals.&lt;/p&gt;
&lt;h2&gt;Enjoy your webserver and Pi Hole together&lt;/h2&gt;
&lt;p&gt;Done. You should now be able to run Pi Hole and a webserver simultaneously and without issues on your Pi.&lt;/p&gt;
</content:encoded></item><item><title>Smart morning radio with Raspberry Pi, cron and MPD</title><link>https://rafaelc.org/posts/smart-morning-radio-with-raspberry-pi-cron-and-mpd</link><guid isPermaLink="true">https://rafaelc.org/posts/smart-morning-radio-with-raspberry-pi-cron-and-mpd</guid><pubDate>Mon, 13 Aug 2018 05:14:19 GMT</pubDate><content:encoded>&lt;p&gt;What if your Pi waked you up every morning with your favorite radio station? Wouldn&apos;t it be cool if it stopped playing when you leave home? And what about not bothering you on holidays?&lt;/p&gt;
&lt;p&gt;I have a pair of speakers connected to my Pi, and I keep them on 24/7. This opens a lot of possibilities. One is to make my Pi stream some radio always at a fixed time.&lt;/p&gt;
&lt;p&gt;After configuring a player such as MPD and obtaining the streaming URL, that becomes fairly easy to do. Just a matter of placing a couple of commands on your crontab.&lt;/p&gt;
&lt;p&gt;I wanted, however, to make something more robust and take care of some details.&lt;/p&gt;
&lt;p&gt;This post walks you through my fairly simple, but convenient setup. &lt;a href=&quot;https://github.com/rc2dev/radioauto&quot;&gt;&lt;strong&gt;If you want to cut through the talk and just take a hint at it, you can check it here.&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Requirements&lt;/h2&gt;
&lt;p&gt;I&apos;ll assume you have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Some speakers connected to your Pi, and configured on MPD&lt;/li&gt;
&lt;li&gt;MPC installed, to control MPD&lt;/li&gt;
&lt;li&gt;Cron installed, to schedule our commands&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Cron and scripting&lt;/h2&gt;
&lt;p&gt;As I said, to schedule our Pi to play radio at specific times, we could just drop something as &lt;code&gt;mplayer url_of_the_stream&lt;/code&gt; on the crontab.&lt;/p&gt;
&lt;p&gt;But what about the volume? And holidays?&lt;/p&gt;
&lt;p&gt;As your command becomes more complex, it makes sense to extract it from the crontab and make it a script.&lt;/p&gt;
&lt;p&gt;While you could write it in any language you like, Bash and Python are sensible choices. I pick Bash.&lt;/p&gt;
&lt;p&gt;In the following sections, we&apos;ll build that script together. &lt;a href=&quot;https://github.com/rc2dev/radioauto/blob/master/bin/radioauto&quot;&gt;You can check mine here.&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;First of all, set the volume!&lt;/h2&gt;
&lt;p&gt;As much as you like a station, you probably don&apos;t want to listen to them on a random volume first thing in the morning. Neither your neighbours.&lt;/p&gt;
&lt;p&gt;So let&apos;s set it to a good level.&lt;/p&gt;
&lt;h3&gt;On ALSA&lt;/h3&gt;
&lt;p&gt;If your Pi uses ALSA, doing it is as simple as this command, replacing 20 for the desired level:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ mpc volume 20
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;On PulseAudio&lt;/h3&gt;
&lt;p&gt;For PulseAudio, things get a bit trickier. In this case, the &lt;code&gt;volume&lt;/code&gt; command adjusts the volume of the MPD sink-input, which is closed whenever MPD stops the playback. This means that, if you issue the same command before anything is playing, you&apos;ll get an error.&lt;/p&gt;
&lt;p&gt;A solution for that is setting &lt;code&gt;always_on = true&lt;/code&gt; for the PulseAudio output in &lt;code&gt;/etc/mpd.conf&lt;/code&gt;. However, doing this made me experience some delays and weird behaviours on MPD. So I did another thing.&lt;/p&gt;
&lt;p&gt;My hacky workaround is to create a silent track and ask MPD to play it. In the meanwhile, I can set the volume. Like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;set_volume() {
  mpc clear
  mpc add /path/to/silence.ogg
  mpc play
  sleep 10
  mpc volume $1
  mpc clear
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Did you unmute?&lt;/h2&gt;
&lt;p&gt;This step may sound silly, but for someone as me who mutes the Pi to do some VNC work, it can actually be a lifesaver.&lt;/p&gt;
&lt;p&gt;My script makes sure my Pi is not muted and it also assures the general volume to be at 100%.&lt;/p&gt;
&lt;p&gt;For PulseAudio:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pactl set-sink-mute sink_name false
pactl set-sink-volume sink_name 100%
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where &lt;code&gt;sink_name&lt;/code&gt; will be something like &lt;code&gt;alsa_output.pci-0000_00_1f.3.analog-stereo&lt;/code&gt;. You can get it issuing:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ pactl list sinks short
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For ALSA, you should run something similar to this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ amixer -c 0 set Master unmute
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check &lt;code&gt;alsamixer&lt;/code&gt; and replace &lt;code&gt;0&lt;/code&gt; and &lt;code&gt;Master&lt;/code&gt; with the values for your setup. We don&apos;t set anything to 100% here, because on ALSA you only have one volume per output (the one we set on the previous step).&lt;/p&gt;
&lt;h2&gt;Holidays!&lt;/h2&gt;
&lt;p&gt;On cron, we&apos;ll make sure that our radio alarm only plays on weekdays. But what about holidays? You probably don&apos;t want to be woken up at 6 am!&lt;/p&gt;
&lt;p&gt;I solved that in a simple way. In a text file I placed all the holidays, one per line, in the &lt;code&gt;mm-dd&lt;/code&gt; format. The ones that change each year, such as Easter, are written as &lt;code&gt;yyyy-mm-dd&lt;/code&gt;. I made the script look for the current day on that file, and to abort if it is found.&lt;/p&gt;
&lt;p&gt;So we got a function like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;check_holiday() {
	if grep -q -e &quot;^\s*$(date +%Y-%m-%d)\s*&quot; -e &quot;^\s*$(date +%m-%d)\s*&quot; &quot;$holidays_file&quot;; then
		printf &quot;Today is a holiday. Not doing anything.\n&quot; &amp;gt;&amp;amp;2
		exit 0
	fi
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Disable dates&lt;/h2&gt;
&lt;p&gt;At times, you may want the radio not to play at some workable dates. Maybe you&apos;ll be out of town, you&apos;ll not go to work for some reason, or you&apos;ll wake up much later.&lt;/p&gt;
&lt;p&gt;While we could use here the same solution that we did for the holidays (placing the dates in a text file), this didn&apos;t cut it for me. Reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You would have to SSH to the Pi and mess with a text file every time you wanted to disable a date.&lt;/li&gt;
&lt;li&gt;Do you ever want other people in your household to use this?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For storing mostly stable values, as in the holidays case, a plain text file works well. But for something more dynamic and ad-hoc as this, I wanted other thing.&lt;/p&gt;
&lt;p&gt;So I coded a simple web app to handle that. Under the hood, it still writes to a plain text file, very similar to the holidays one, with the script grepping it the same way. It does the management of that file and offers a friendly interface.&lt;/p&gt;
&lt;h2&gt;Let&apos;s play!&lt;/h2&gt;
&lt;p&gt;Now that we checked the day is not a holiday nor a undesired date and properly set the volume, it&apos;s time to play our radio!&lt;/p&gt;
&lt;p&gt;For this, I&apos;d also recommend to have a backup station, in case the streaming URL of your favorite radio gets broken. You could also add a song from your library, in case there are any network problems. This way, you make sure something will always play.&lt;/p&gt;
&lt;p&gt;One possible way:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mpc clear
mpc add https://example.com/preferred_stream
mpc add https://example.com/backup_stream
mpc add &quot;A song.ogg&quot;
mpc random off
mpc play
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can get creative here. For example, you could add many radios, maybe using a playlist, and turn random on. So each day a different station would wake you up.&lt;/p&gt;
&lt;p&gt;On my script, instead of passing some URLs through MPC, I call the &lt;a href=&quot;https://rafaelc.org/pifi&quot;&gt;PiFi Radio (a MPD web client to listen to radio that I wrote)&lt;/a&gt; API with the radio names. This is more convenient to me, since I can store all the streaming URLs in a single place.&lt;/p&gt;
&lt;h2&gt;What about stopping?&lt;/h2&gt;
&lt;p&gt;The same way I wanted my Pi to play radio automatically every morning, it would be nice that would stop playing it without human intervention.&lt;/p&gt;
&lt;p&gt;While we could get fancy here and try to detect when we leave home, I decided to just schedule a reasonable time for the playback to stop.&lt;/p&gt;
&lt;p&gt;At the given time, cron calls the necessary steps to do it, which are simply:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mpc stop
set_volume 50
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where &lt;code&gt;set_volume&lt;/code&gt; is the function we&apos;ve written before.&lt;/p&gt;
&lt;h2&gt;Let&apos;s schedule it&lt;/h2&gt;
&lt;p&gt;Now that our script is done, we need to make it run at the desired time. For that, we&apos;ll use cron. Alternatively, you could use systemd timers. I&apos;ll do it old school.&lt;/p&gt;
&lt;p&gt;You can add the script to your personal crontab, running:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ crontab -e
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I dropped it under &lt;code&gt;/etc/cron.d&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# /etc/cron.d/radioauto

# Play radio automagically in week days
00 06 * * 1-5 pi /opt/scripts/radioauto start
00 08 * * 1-5 pi /opt/scripts/radioauto stop
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will run the script from Monday to Friday at 6 and 8 am. First with the &lt;code&gt;start&lt;/code&gt; argument, than with &lt;code&gt;stop&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you copy and paste those lines to a user crontab, the columns are a bit different. Be sure to remove the user column (the one which says &lt;code&gt;pi&lt;/code&gt;).&lt;/p&gt;
&lt;h2&gt;The result&lt;/h2&gt;
&lt;p&gt;And we are done. Our Pi will wake us up every day with our favourite radio. It will stop playing at a given time automatically. And we won&apos;t be bothered on holidays and whenever we don&apos;t want it.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/rc2dev/radioauto&quot;&gt;You can check my setup here.&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>Export all your Kindle highlights and notes</title><link>https://rafaelc.org/posts/export-all-your-kindle-highlights-and-notes</link><guid isPermaLink="true">https://rafaelc.org/posts/export-all-your-kindle-highlights-and-notes</guid><pubDate>Thu, 19 Sep 2019 17:37:40 GMT</pubDate><content:encoded>&lt;p&gt;Previously, &lt;a href=&quot;/posts/digital-reading-highlights-and-notes&quot;&gt;I discussed the current state of annotations in digital books and documents&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In this post, I want to introduce a tool I developed to help Kindle users handle their highlights, notes, bookmarks, etc.&lt;/p&gt;
&lt;h2&gt;Getting annotations from your Kindle&lt;/h2&gt;
&lt;p&gt;So you&apos;ve been reading on your Kindle device. You highlighted some interesting parts. You wrote important notes. And you bookmarked relevant pages. Now how do you access it outside of the Kindle?&lt;/p&gt;
&lt;p&gt;The Kindle has a mobile app and a web page where you can see your annotations and also export them. But whether your annotations for a particular book/document will be available there depends on how you acquired it.&lt;/p&gt;
&lt;p&gt;I made a table to illustrate how it currently works (2019):&lt;/p&gt;
&lt;p&gt;&lt;a name=&quot;table&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Got from...&lt;/th&gt;
&lt;th&gt;Mobile app&lt;/th&gt;
&lt;th&gt;Web&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Bought on Amazon&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sent via e-mail&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stored via USB&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;So if you bought the book from Amazon, you have the best of worlds. You can access your annotations from Amazon&apos;s mobile app or &lt;a href=&quot;https://read.amazon.com/&quot;&gt;the web&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you sent the content to your Kindle e-mail, Amazon automatically tagged it as &quot;personal document&quot; (even if it is a book). Its annotations will only be available on the mobile app.&lt;/p&gt;
&lt;p&gt;Lastly, if you added content to your Kindle via USB, bad luck. No way to access its annotations or the content out of the Kindle.&lt;/p&gt;
&lt;h2&gt;My Clippings to the rescue&lt;/h2&gt;
&lt;p&gt;So we get a somewhat fragmented experienced. Depending on how you acquired each content, its annotations are available on one place or another; and thus are easily exportable or not.&lt;/p&gt;
&lt;p&gt;Luckily, the Kindle stores a plain text file on its &lt;code&gt;documents&lt;/code&gt; folder called &lt;code&gt;My Clippings&lt;/code&gt;. For each highlight, note, bookmark and clip you set while on your Kindle, it appends this file with the corresponding information.&lt;/p&gt;
&lt;h2&gt;My Clippings shortcomes&lt;/h2&gt;
&lt;p&gt;Now, this file is very handy, no doubt. But yet it is kind of a mess. As I said, it is appended whenever you do one of the above operations. This means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It is not ordered by document, but by time. So, for instance, if you switch reading between two books, you&apos;ll get some annotations for one, then for the other, then for the first again, etc.&lt;/li&gt;
&lt;li&gt;As the entries are ordered by time, if you annotate something in page 100, and later on page 50, the later will be after the former.&lt;/li&gt;
&lt;li&gt;If you edit or delete a highlight, note, etc, the file doesn&apos;t reflect it. It adds the newly edition, but keeps the previous one. It doesn&apos;t delete any entry. The result is tons of similar, duplicated, or previously removed entries floating on there.&lt;/li&gt;
&lt;li&gt;For every append, it adds info about the correspondent book, the time the append was done, etc. The actual content can be missed with all that noise, making the file difficult to read.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Solutions available&lt;/h2&gt;
&lt;p&gt;So while having tons of valuable data, this file is not really readable nor easily browseable. The obvious thing to do was to try to parse it. I looked for applications to do that and found a few: web and desktop. However, none satisfied me:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Most of them didn&apos;t work. While I didn&apos;t investigate the causes for each codebase, some failed because locale specific strings were hardcoded.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Web based solutions seemed to add complexity to the problem. Having to periodically upload &lt;code&gt;My Clippings&lt;/code&gt; to a platform, manage my data there in a complex interface and then export my annotations again seemed overkill.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I didn&apos;t try all the applications available. I quickly realized that it would be much better to code my own solution from ground up, than testing many until one worked or reasonably satisfied me. Besides, I still would have to adapt them to my needs.&lt;/p&gt;
&lt;h2&gt;Meet Fyodor&lt;/h2&gt;
&lt;p&gt;So I created a tool to solve those problems. It&apos;s &lt;a href=&quot;https://github.com/rc2dev/fyodor&quot;&gt;Fyodor&lt;/a&gt;, written in Ruby and libre and open-source.&lt;/p&gt;
&lt;p&gt;Fyodor parses all the precious data from &lt;code&gt;My Clippings&lt;/code&gt; and makes it readable. It generates well formatted markdown files: one per book or document that you read.&lt;/p&gt;
&lt;p&gt;Some of the features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Your highlights and notes are ordered by page &lt;em&gt;(not time)&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Cleans up empty and duplicated entries as possible.&lt;/li&gt;
&lt;li&gt;Tries to be locale agnostic, and allows configuration for different Kindle languages.&lt;/li&gt;
&lt;li&gt;The output is easily editable and read by virtually any computer &lt;em&gt;(plain text)&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/rc2dev/fyodor&quot;&gt;&lt;em&gt;And more...&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The workflow, pros and cons&lt;/h2&gt;
&lt;p&gt;To extract your Kindle annotations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Connect your Kindle via USB.&lt;/li&gt;
&lt;li&gt;Copy &lt;code&gt;My Clippings&lt;/code&gt; from &lt;code&gt;documents&lt;/code&gt; folder to your computer.&lt;/li&gt;
&lt;li&gt;Run Fyodor.&lt;/li&gt;
&lt;li&gt;Now you have a bunch of markdown files, one for each document. Edit them as you will.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Advantages of this method:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You extract all the annotations from &lt;em&gt;all&lt;/em&gt; your Kindle content with one command.&lt;/li&gt;
&lt;li&gt;You don&apos;t need to remember how each document got to your Kindle just to export its annotations.&lt;/li&gt;
&lt;li&gt;The content you loaded via USB can finally have their annotations set free from the device &lt;em&gt;(&lt;a href=&quot;#table&quot;&gt;remember the table?&lt;/a&gt;)&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;All your annotations are stored in one single folder, conveniently organized per book.&lt;/li&gt;
&lt;li&gt;Clean and editable output.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Of course, we still have some limitations that the Kindle imposes us:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We can&apos;t guess all highlights and notes that were deleted.&lt;/li&gt;
&lt;li&gt;We don&apos;t have chapter information.&lt;/li&gt;
&lt;li&gt;We need to transfer &lt;code&gt;My Clippings&lt;/code&gt; via USB.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But yet, this is a much better state of affairs, and I&apos;m fairly satisfied. I hope this app makes your life better. &lt;a href=&quot;https://github.com/rc2dev/fyodor&quot;&gt;Check it out here.&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>Send files to your phone from file manager</title><link>https://rafaelc.org/posts/send-files-to-your-phone-from-file-manager</link><guid isPermaLink="true">https://rafaelc.org/posts/send-files-to-your-phone-from-file-manager</guid><pubDate>Wed, 16 Oct 2019 20:55:41 GMT</pubDate><content:encoded>&lt;p&gt;A very handy thing to have in your file manager is an option to send files directly to your phone.&lt;/p&gt;
&lt;p&gt;If you happen to use KDE Connect and Dolphin, this is already there out of the box. Just right click a file and will see an option to send it to your devices. GNOME and Nautilus with the GSConnect extension provides the same.&lt;/p&gt;
&lt;p&gt;But what if you use other file manager? You can replicate that functionality, and this post aims to help you on that.&lt;/p&gt;
&lt;p&gt;The general idea is to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Craft the right command to send the file.&lt;/li&gt;
&lt;li&gt;Instruct the file manager to have it as a context menu item.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So let&apos;s do it.&lt;/p&gt;
&lt;h2&gt;Step 1 - Install and configure KDE Connect&lt;/h2&gt;
&lt;p&gt;First of all, you need to have KDE Connect installed (both on your computer and your phone) and do the pairing.&lt;/p&gt;
&lt;h2&gt;Step 2 - Craft the command&lt;/h2&gt;
&lt;p&gt;KDE Connect has a CLI, &lt;code&gt;kdeconnect-cli&lt;/code&gt;. Running it with &lt;code&gt;-l&lt;/code&gt; provides a list of paired devices:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ kdeconnect-cli -l
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To send them a file, you can run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ kdeconnect-cli -d DEVICE_ID --share FILE_PATH
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ kdeconnect-cli -n DEVICE_NAME --share FILE_PATH
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Take a look at the output of the first command and replace &lt;code&gt;DEVICE_ID&lt;/code&gt; or &lt;code&gt;DEVICE_NAME&lt;/code&gt; accordingly, as well as &lt;code&gt;FILE_PATH&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Step 3 - Setting the command on the file manager&lt;/h2&gt;
&lt;p&gt;Now you are able to send files to your phone by typing a command. The next step is to configure your file manager to offer to run that same command when you select a file.&lt;/p&gt;
&lt;p&gt;The precise way to do that depends on which file manager you are using. I&apos;ll briefly explain it for Thunar and Nemo.&lt;/p&gt;
&lt;h3&gt;Thunar&lt;/h3&gt;
&lt;p&gt;On Thunar, add a custom action.&lt;/p&gt;
&lt;p&gt;Click on &lt;em&gt;Edit - Configure custom actions&lt;/em&gt;. After clicking the &lt;code&gt;+&lt;/code&gt; icon, paste the command you got from &lt;em&gt;Step 2&lt;/em&gt;, replacing the file path by &lt;code&gt;%f&lt;/code&gt;. You&apos;ll have something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;kdeconnect-cli -d DEVICE_ID --share %f
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the tab &lt;em&gt;&quot;Appearance conditions&quot;&lt;/em&gt; be sure to check all file types except &lt;em&gt;&quot;Directories&quot;&lt;/em&gt;, as &lt;code&gt;kdeconnect-cli&lt;/code&gt; can&apos;t handle them.&lt;/p&gt;
&lt;h3&gt;Nemo&lt;/h3&gt;
&lt;p&gt;On Nemo, you can create a Nemo action. These are files very similar to desktop entries and are placed at &lt;code&gt;~/.local/share/nemo/actions/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/rc2dev/678adc40cd75f9ea472cd3f77b63d21c#file-send2phone-nemo_action&quot;&gt;You can get mine here&lt;/a&gt;. Replace what is after &lt;code&gt;Exec=&lt;/code&gt; with the command from &lt;em&gt;Step 2&lt;/em&gt;, and change &lt;code&gt;Selection=m&lt;/code&gt; to &lt;code&gt;Selection=s&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Improving: Multiple files and notifications&lt;/h2&gt;
&lt;p&gt;If everything went well, you can now send a file to your phone just by right clicking it and selecting the option. Cool, uh?&lt;/p&gt;
&lt;p&gt;However, there is room for improvement. As I pointed out, this setup doesn&apos;t allow you to send more than one file per time. That&apos;s a limitation from &lt;code&gt;kdeconnect-cli&lt;/code&gt;. Also, you may want a notification if the sending was successful or not.&lt;/p&gt;
&lt;p&gt;Both things can be achieved by writing a small wrapper script around &lt;code&gt;kdeconnect-cli&lt;/code&gt;, and making your file manager call it instead of the former. Then, make the file manager allow multiple selection. For this, on Thunar replace &lt;code&gt;%f&lt;/code&gt; with &lt;code&gt;%F&lt;/code&gt; in your command; on Nemo, set &lt;code&gt;Selection=m&lt;/code&gt; in your action.&lt;/p&gt;
&lt;p&gt;If those improvements interest you, take a look at the next section.&lt;/p&gt;
&lt;h2&gt;My setup&lt;/h2&gt;
&lt;p&gt;I wrote a small &lt;a href=&quot;https://gist.github.com/rc2dev/678adc40cd75f9ea472cd3f77b63d21c#file-send2phone&quot;&gt;wrapper script&lt;/a&gt;, which takes care of all the points above.&lt;/p&gt;
&lt;p&gt;Some readers asked me for a step-by-step guide on setting it up, so I&apos;ll describe it below.&lt;/p&gt;
&lt;h3&gt;On Nemo&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Download the script to your PATH and make it executable:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;$ cd /usr/local/bin    # or anywhere in your PATH
$ sudo wget https://gist.githubusercontent.com/rc2dev/678adc40cd75f9ea472cd3f77b63d21c/raw/893032b9c60f510727fed20548a6be93e1048e81/send2phone -O send2phone
$ sudo chmod +x send2phone
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Download the Nemo action and place it in the appropriate directory:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;$ mkdir -p ~/.local/share/nemo/actions
$ cd ~/.local/share/nemo/actions
$ wget https://gist.githubusercontent.com/rc2dev/678adc40cd75f9ea472cd3f77b63d21c/raw/893032b9c60f510727fed20548a6be93e1048e81/send2phone.nemo_action -O send2phone.nemo_action
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;On Thunar&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Download the script to your PATH and make it executable:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;$ cd /usr/local/bin    # or anywhere in your PATH
$ sudo wget https://gist.githubusercontent.com/rc2dev/678adc40cd75f9ea472cd3f77b63d21c/raw/893032b9c60f510727fed20548a6be93e1048e81/send2phone -O send2phone
$ sudo chmod +x send2phone
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Open Thunar and create a custom action with the following command:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;send2phone %F
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As always, on &lt;em&gt;&quot;Appearance conditions&quot;&lt;/em&gt; check everything except directories.&lt;/p&gt;
&lt;h3&gt;On ranger&lt;/h3&gt;
&lt;p&gt;If you prefer a terminal-based file manager, things are analogous. On ranger, just download the script to your PATH and add a line to your &lt;code&gt;rc.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;map bp shell -f send2phone %s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you can select some files, press &lt;code&gt;bp&lt;/code&gt; (or whatever shortcut you prefer) and they are sent to your phone.&lt;/p&gt;
&lt;h2&gt;Troubleshooting &quot;No such object path&quot;&lt;/h2&gt;
&lt;p&gt;If &lt;code&gt;kdeconnect-cli&lt;/code&gt; is throwing a message like &lt;code&gt;error: No such object path&lt;/code&gt;, &lt;a href=&quot;/posts/a-workaround-for-kde-connect-browsing-bug&quot;&gt;take a look here&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Mount Borg backups with ease</title><link>https://rafaelc.org/posts/mount-borg-backups-with-ease</link><guid isPermaLink="true">https://rafaelc.org/posts/mount-borg-backups-with-ease</guid><pubDate>Mon, 07 Oct 2019 17:20:07 GMT</pubDate><content:encoded>&lt;p&gt;Borg is an excellent backup software. One of Borg&apos;s features is mounting the backups and accessing all files in them. This is done by running &lt;code&gt;borg mount&lt;/code&gt; with the path to the repository and the name of the archive you want to mount.&lt;/p&gt;
&lt;p&gt;My workflow for mounting a Borg archive is typically like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Make an empty directory for the mounting, taking care of the permissions.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Determine which repository contains the archive I want to mount and &lt;code&gt;cd&lt;/code&gt; into it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Export my Borg passphrase, so I&apos;m not asked for it multiple times.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run &lt;code&gt;borg list .&lt;/code&gt; to list the archives on that repository and decide for one of those.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Mount the archive with &lt;code&gt;borg .::${archive_name} ${mount_path}&lt;/code&gt;. This is the most tedious step since we need to type the full archive name and the mount path.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note from 2023-06-07: This became easier as Borg added support for shell tab completion. Also, you can now mount the entire repository instead of choosing an archive in advance.&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;cd&lt;/code&gt; into the mount point or open my file manager and browse to it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;After I&apos;m done, remember to type a command to umount the archive.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Delete the mount point that I created.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&apos;s easy to see how repeatedly typing those commands can become tedious and frustrating, not to mention error prone.&lt;/p&gt;
&lt;p&gt;So I wrote a simple Bash script to automate this task.&lt;/p&gt;
&lt;h2&gt;The solution&lt;/h2&gt;
&lt;p&gt;It works like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It first lists all your archives.&lt;/li&gt;
&lt;li&gt;You select one by simply typing a number.&lt;/li&gt;
&lt;li&gt;The archive is mounted and your file manager pops up with it &lt;em&gt;(if you are using a graphical interface)&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;After you are done, you can just press enter and the archive will be umounted.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So from all that huge list of commands, we came to just type this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ borg-mount /repo/path
foobar # type the passphrase (if not exported)
8  # the archive number
↵  # umount when done
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Cool, uh?&lt;/p&gt;
&lt;h2&gt;Downloading and running&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/rc2dev/dotfiles/blob/master/.local/bin/borg-mount&quot;&gt;You can find the script here.&lt;/a&gt; Near the top you&apos;ll find a configuration section with the values to pass to &lt;code&gt;borg mount&lt;/code&gt; command. You&apos;ll probably want to change the file manager command and empty the other values before first running it.&lt;/p&gt;
</content:encoded></item><item><title>A workaround for KDE Connect browsing bug</title><link>https://rafaelc.org/posts/a-workaround-for-kde-connect-browsing-bug</link><guid isPermaLink="true">https://rafaelc.org/posts/a-workaround-for-kde-connect-browsing-bug</guid><pubDate>Sat, 16 Nov 2019 07:51:02 GMT</pubDate><content:encoded>&lt;p&gt;If you press &lt;em&gt;&quot;Browse device&lt;/em&gt;&quot; on the KDE Connect tray icon and get the following error:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;A folder named ~/.cache/kioexec/krun/&amp;lt;some_numbers&amp;gt;/ already exists.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In this post I will show you a simple workaround: open your file manager and browse to &lt;code&gt;/run/user/&amp;lt;YOUR UID&amp;gt;/&amp;lt;YOUR DEVICE ID&amp;gt;/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you are unsure of your UID, try &lt;code&gt;1000&lt;/code&gt;, or issue &lt;code&gt;id&lt;/code&gt; or &lt;code&gt;echo $UID&lt;/code&gt; in the terminal.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;YOUR DEVICE ID&lt;/code&gt; is a hexadecimal string and should be obvious when you get to the parent directory. In any case, you can find it out running &lt;code&gt;kdeconnect-cli -l&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That&apos;s it! Now you should see your device files, despite of the previous error.&lt;/p&gt;
&lt;h2&gt;So what happened?&lt;/h2&gt;
&lt;p&gt;Apparently, when you click &lt;em&gt;&quot;Browse device&quot;&lt;/em&gt; , KDE Connect succeeds to mount the filesystem, but fails to bring your file manager to show it. When you browse to the mount path, everything is there.&lt;/p&gt;
&lt;p&gt;The bug is already fixed upstream, so in the future a update from your chosen distro should deal with it. The system in which I see this issue is Linux Mint 19.2 Cinnamon, with Nemo file manager and KDE Connect 1.3.3.&lt;/p&gt;
&lt;p&gt;In the meantime, you can use this workaround. Also, some people reported that installing Dolphin fixes the issue, so you might try it if it sounds appealing.&lt;/p&gt;
</content:encoded></item><item><title>Watching YouTube playlists on shuffle with MPV</title><link>https://rafaelc.org/posts/watching-youtube-playlists-on-shuffle-with-mpv</link><guid isPermaLink="true">https://rafaelc.org/posts/watching-youtube-playlists-on-shuffle-with-mpv</guid><pubDate>Sat, 06 Jun 2020 21:32:25 GMT</pubDate><content:encoded>&lt;p&gt;Right now, I have a bunch of videos in my YouTube watch later playlist. After a certain number, browsing through them becomes a pain. So I thought of watching them randomly, and skip whatever didn&apos;t interested me at the moment.&lt;/p&gt;
&lt;p&gt;YouTube does have a shuffle button, but it never worked well when I tried it. Also, I wanted to use MPV, which gives me better performance on Linux and a nice experience.&lt;/p&gt;
&lt;h2&gt;A first approach&lt;/h2&gt;
&lt;p&gt;Initially, I thought of a very nixy solution, combining some utilities to get the job done:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Use &lt;code&gt;yt-dlp&lt;/code&gt; to get the list of URLs on my watch later playlist.&lt;/li&gt;
&lt;li&gt;Shuffle it with &lt;code&gt;shuf&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Make &lt;code&gt;mpv&lt;/code&gt; run each video.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;However...&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;MPV has a playlist feature. Instead of using &lt;code&gt;shuf&lt;/code&gt; and calling &lt;code&gt;mpv&lt;/code&gt; for each video, we could let it handle the whole playlist. This would give us more possibilities such as easily toggling shuffle on and off and going to previous videos.&lt;/li&gt;
&lt;li&gt;MPV has a yt-dlp hook. If we pass a playlist URL, it will automatically retrieve the list of videos. This could simplify our code further.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;So my solution came to this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ mpv --shuffle --ytdl-raw-options=&quot;cookies-from-browser=&amp;lt;browser&amp;gt;&quot; &quot;https://youtube.com/playlist?list=WL&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&apos;s take a look at the arguments:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The first one, &lt;code&gt;--shuffle&lt;/code&gt;, is self-explanatory. We want the videos to play randomly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The second argument passes your browser cookies to yt-dlp. This is necessary in the case of a private playlist, which your &quot;watch later&quot; probably is. Replace &lt;code&gt;&amp;lt;browser&amp;gt;&lt;/code&gt; with your browser&apos;s name. For example, if you use the Chrome browser, use &lt;code&gt;--ytdl-raw-options=&quot;cookies-from-browser=chrome&quot;&lt;/code&gt;. Check &lt;code&gt;yt-dlp&lt;/code&gt; documentation for the supported browsers.&lt;/p&gt;
&lt;p&gt;As an alternative, you can pass a cookies file to yt-dlp. You can easily obtain it using an extension for your browser. Then,use with &lt;code&gt;--ytdl-raw-options=&quot;cookies=path/to/cookies.txt&quot;&lt;/code&gt;, replacing the path to &lt;code&gt;cookies.txt&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The last argument is the playlist URL; in this case the watch later playlist.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you already have your cookies configured on your yt-dlp configuration, or if the playlist is public, you won&apos;t need the second argument, and the command will be as simple as:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ mpv --shuffle &quot;https://youtube.com/playlist?list=WL&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Just be sure to have MPV and yt-dlp installed.&lt;/p&gt;
&lt;h2&gt;Usage&lt;/h2&gt;
&lt;p&gt;Running this, MPV will pop with a random video from your watch later playlist.&lt;/p&gt;
&lt;p&gt;To go to the next (random) video, the default MPV keybinds are &lt;code&gt;&amp;gt;&lt;/code&gt; and &lt;code&gt;Enter&lt;/code&gt;. To go the previous, &lt;code&gt;&amp;lt;&lt;/code&gt;. Close MPV with &lt;code&gt;q&lt;/code&gt;. You also have the current and previous videos URLs printed in the terminal, so you can easily visit their pages if you need to.&lt;/p&gt;
&lt;h2&gt;Open the video page with a keybinding&lt;/h2&gt;
&lt;p&gt;While watching my playlists on MPV, I often want to open the current video in the browser.&lt;/p&gt;
&lt;p&gt;MPV makes it easy by printing the URL of the current video in the terminal. However, I can&apos;t make use of that, because:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;My window manager swallows the terminal.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/posts/how-i-use-the-open-with-extension&quot;&gt;If I invoke it via &quot;Open with&quot;&lt;/a&gt; or dmenu, I won&apos;t get the terminal output by default.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Besides, it would be nice to have a keybinding for that, instead of clicking the URL.&lt;/p&gt;
&lt;p&gt;Implementing this is as simple as dropping a line at MPV&apos;s &lt;code&gt;input.conf&lt;/code&gt; or &lt;a href=&quot;https://github.com/nimatrueway/mpv-locatefile-lua-script&quot;&gt;using this plugin&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Invocation with dmenu&lt;/h2&gt;
&lt;p&gt;Besides &quot;watch later&quot;, I sometimes watch other playlists. Copying and pasting their URLs every time would be tedious. Instead, &lt;a href=&quot;https://github.com/rc2dev/dotfiles/blob/master/.local/bin/dmenu-yt&quot;&gt;I wrote a dmenu script&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Pressing a keybind on my computer, dmenu shows all the playlists from my logged account and let me pick one to watch. After that, MPV starts the playback on shuffle mode. This is very convenient and much better than browsing YouTube.&lt;/p&gt;
&lt;figure&gt;
  &lt;img src=&quot;/assets/posts-media/yt-shuffle-dmenu.png&quot; alt=&quot;My playlists on dmenu.&quot; /&gt;
  &lt;figcaption&gt;My playlists on dmenu.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;If you don&apos;t use dmenu, you can adapt this idea. You just need some kind of menu from which to choose the playlist and call the correct MPV command.&lt;/p&gt;
&lt;h2&gt;Invocation from browser&lt;/h2&gt;
&lt;p&gt;Another convenient way to watch different playlists is to invoke MPV directly from the browser.&lt;/p&gt;
&lt;p&gt;I do that with &lt;a href=&quot;https://github.com/darktrojan/openwith&quot;&gt;a browser extension called Open With&lt;/a&gt;. This extension allows you to send the URL of your current tab or a right-clicked link to a program of your choice. &lt;em&gt;Btw, &lt;a href=&quot;/posts/how-i-use-the-open-with-extension&quot;&gt;I wrote a whole article about how I use that extension&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Download the extension and add an entry for the command we crafted, replacing the playlist URL with &lt;code&gt;%s&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ mpv --shuffle --ytdl-raw-options=&quot;cookies-from=&amp;lt;browser&amp;gt;&quot; %s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As I have a keybinding on MPV for shuffling and the cookies are taken care by my yt-dlp configuration, I simply use:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ mpv %s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, when you visit a playlist and click the entry, MPV will pop with that playlist on shuffle.&lt;/p&gt;
&lt;p&gt;Additionally, this entry will allow you to watch individual videos, if you select it on a video page.&lt;/p&gt;
&lt;p&gt;Happy watching!&lt;/p&gt;
</content:encoded></item><item><title>How I use the Open With extension</title><link>https://rafaelc.org/posts/how-i-use-the-open-with-extension</link><guid isPermaLink="true">https://rafaelc.org/posts/how-i-use-the-open-with-extension</guid><pubDate>Sun, 27 Sep 2020 00:31:18 GMT</pubDate><content:encoded>&lt;p&gt;A very useful browser extension is &lt;a href=&quot;https://github.com/darktrojan/openwith&quot;&gt;Open With&lt;/a&gt;. Although it hasn&apos;t been updated for a while, I&apos;ve yet to find something to replace it.&lt;/p&gt;
&lt;p&gt;Open With describes itself in the Chrome web store as the following: &lt;em&gt;&quot;Quickly test out your web pages in Edge, Firefox, Safari, or Opera.&quot;&lt;/em&gt; However, it is much more powerful than that: it actually allows you to run any external program on your computer passing the URL of the current tab or the selected link.&lt;/p&gt;
&lt;p&gt;After installing the extension, you should define a list of commands. The following are some from my list.&lt;/p&gt;
&lt;h2&gt;Other browsers&lt;/h2&gt;
&lt;p&gt;The most obvious use for this extension is to open a page in other browser. For that, you should add your browsers to the list. Thankfully, this is easy, as the extension tries to automatically find them all.&lt;/p&gt;
&lt;h2&gt;Incognito mode&lt;/h2&gt;
&lt;p&gt;Sometimes you want to open the page as incognito. So, for each browser (even the current one), I also add an entry for launching a page in their incognito mode / private window.&lt;/p&gt;
&lt;h2&gt;Browser temporary profile&lt;/h2&gt;
&lt;p&gt;Now and then you might stumble upon a broken webpage and wonder if is conflicting with some of your extensions or settings. In that case, opening it in a clean browser profile might be useful.&lt;/p&gt;
&lt;p&gt;The exact command for running a temporary profile depends on your browser. For some, like Chromium, you just run &lt;code&gt;chromium --temp-profile&lt;/code&gt;. For others, you are better off writing a small script for it.&lt;/p&gt;
&lt;p&gt;Place the command at your Open With list, and now with just a click you open the page in a clean browser profile that will be removed from disk just after use.&lt;/p&gt;
&lt;h2&gt;MPV&lt;/h2&gt;
&lt;p&gt;Other of my entries is MPV. This allows me to play not only videos, but also entire YouTube playlists in that fantastic player. &lt;a href=&quot;/posts/watching-youtube-playlists-on-shuffle-with-mpv&quot;&gt;By the way, I wrote a whole article about it.&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;youtube-dl&lt;/h2&gt;
&lt;p&gt;With a click, the page will be sent to youtube-dl, which will try to download the video or playlist for you. I also have a separate entry for &lt;code&gt;youtube-dl -x&lt;/code&gt; which just downloads the audio. Please, don&apos;t use it for anything illegal.&lt;/p&gt;
&lt;h2&gt;Wishlist&lt;/h2&gt;
&lt;p&gt;Some things I miss on this extension:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/darktrojan/openwith/issues/233&quot;&gt;Support for sending all tabs at once.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/darktrojan/openwith/issues/118&quot;&gt;Export/import option&lt;/a&gt;. The lack of it makes switching browsers or profiles a pain.&lt;/li&gt;
&lt;li&gt;Better keyboard bindings.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Update&lt;/h2&gt;
&lt;p&gt;Since 2021, I&apos;ve replaced Open With with a dmenu script I wrote and attached to a keybinding. This way, all the above points in the wishlist were accomplished. Also, it works with any browser and any profile without configuration.&lt;/p&gt;
</content:encoded></item><item><title>Convert Kindle annotations from HTML to markdown</title><link>https://rafaelc.org/posts/convert-kindle-annotations-from-html-to-markdown</link><guid isPermaLink="true">https://rafaelc.org/posts/convert-kindle-annotations-from-html-to-markdown</guid><pubDate>Fri, 08 Jan 2021 04:14:52 GMT</pubDate><content:encoded>&lt;p&gt;The Kindle app on your phone can export your annotations on a particular book to an HTML file.&lt;/p&gt;
&lt;p&gt;However, I find that file quite noisy, and would rather have my annotations in a simpler format: markdown. Also, I would like to remove unnecessary information and make it less verbose.&lt;/p&gt;
&lt;p&gt;For the first books, I did this conversion in a couple of minutes using macros in VIM. But as I needed to do it more and more often, the procedure quickly became tedious, and I wanted to automate it.&lt;/p&gt;
&lt;p&gt;So I wrote a Python script to the job. &lt;a href=&quot;https://rafaelc.org/k/kindle2md&quot;&gt;The code is here.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;By the way, if you are interested in exporting all your annotations from your Kindle directly to markdown, from your clippings file, &lt;a href=&quot;https://github.com/rc2dev/fyodor&quot;&gt;take a look at my project Fyodor&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Reading random Pocket articles with a hotkey</title><link>https://rafaelc.org/posts/reading-random-pocket-articles-with-a-hotkey</link><guid isPermaLink="true">https://rafaelc.org/posts/reading-random-pocket-articles-with-a-hotkey</guid><pubDate>Fri, 11 Dec 2020 17:21:55 GMT</pubDate><content:encoded>&lt;p&gt;I have tons of articles in my Pocket account. To make it easier to read them, I wanted a way to get a random one on demand.&lt;/p&gt;
&lt;p&gt;As it turned out, this was very straightforward. Pocket has &lt;a href=&quot;https://getpocket.com/random&quot;&gt;a URL&lt;/a&gt; that gives you exactly that: a random article from your list each time you visit it.&lt;/p&gt;
&lt;p&gt;So far so good. However, when I get a random article, I may not like it. Like a roulette, I may want to spin it again and again until I find something I want to read at a given moment. This means that I usually visit that URL many times in a row.&lt;/p&gt;
&lt;p&gt;This could be as easy as reloading the page, but that URL actually redirects you to the article&apos;s page, so doing this won&apos;t get you anywhere. My options were to click that page on my bookmarks bar repeatedly, or to write that address on the omnibar multiple times. None of this is exactly enjoyable.&lt;/p&gt;
&lt;p&gt;For a much better experience, I wanted to bind a keyboard shortcut for that URL. This way, every time I pressed some key combination, my browser would show a different article from my Pocket list. Cool, right?&lt;/p&gt;
&lt;p&gt;To achieve this, my browser doesn&apos;t support this feature natively, neither could I find a working extension for this. So I solved it by coding a small userscript and dropping it at ViolentMonkey.&lt;/p&gt;
&lt;p&gt;Done. But there is one last problem. After you read an article, you probably want to do something with it on Pocket, maybe archive it or delete it, right? However, this &quot;random&quot; URL brings you directly to the actual page, not Pocket&apos;s article view. Thus, after each page you read, you&apos;d have to open Pocket, search for the article you just read and only then do whatever you wanted to do. Not very nice either.&lt;/p&gt;
&lt;p&gt;I made it much easier by adding another key combination. This one searches for the current page on the Pocket list. Now you are just a few clicks away from archiving the article.&lt;/p&gt;
&lt;h2&gt;Installation and usage&lt;/h2&gt;
&lt;p&gt;If you want to try my solution, install a extension &lt;a href=&quot;https://violentmonkey.github.io/&quot;&gt;such as ViolentMonkey&lt;/a&gt; and &lt;a href=&quot;https://greasyfork.org/pt-BR/scripts/418513-hotkey-for-random-pocket-article&quot;&gt;install my userscript from here&lt;/a&gt;. Don&apos;t forget to reload your current tab.&lt;/p&gt;
&lt;p&gt;Now pressing &lt;code&gt;Alt+Shift+R&lt;/code&gt; brings you a random Pocket article, and &lt;code&gt;Alt+Shift+E&lt;/code&gt; searches for the current page on your Pocket list.&lt;/p&gt;
</content:encoded></item><item><title>How to automatically reflect vim-plug changes on all hosts</title><link>https://rafaelc.org/posts/how-to-automatically-reflect-vim-plug-changes-on-all-hosts</link><guid isPermaLink="true">https://rafaelc.org/posts/how-to-automatically-reflect-vim-plug-changes-on-all-hosts</guid><pubDate>Sat, 06 Mar 2021 02:24:49 GMT</pubDate><content:encoded>&lt;p&gt;I use the excellent &lt;a href=&quot;https://github.com/junegunn/vim-plug&quot;&gt;vim-plug&lt;/a&gt; to manage my VIM plugins. In this setup, whenever you want to add or remove plugins, you have to do three things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Edit (and source) &lt;code&gt;vimrc&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Call a command to install the new plugins, &lt;code&gt;:PlugInstall&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Optionally, call a command to clean the removed ones, &lt;code&gt;:PlugClean&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you use VIM on a single machine, this is straightforward enough. However, if you implement your dotfiles across multiple hosts, simply pulling the changes on &lt;code&gt;vimrc&lt;/code&gt; doesn&apos;t make new plugins work. Every time you change your VIM plugins list, you need to remember to also do 2 and 3 on each host.&lt;/p&gt;
&lt;p&gt;This was painful to me and I wanted to automate the process. My goal was that whenever I pulled my dotfiles on any device and my &lt;code&gt;vimrc&lt;/code&gt; had changed, those commands would run automatically.&lt;/p&gt;
&lt;h2&gt;The solution: Git hooks&lt;/h2&gt;
&lt;p&gt;My dotfiles are under source control, so I immediately thought of Git hooks. They reside under your repo&apos;s &lt;code&gt;.git/hooks&lt;/code&gt; directory and allow you to do exactly that: before or after a certain Git event, run a certain command.&lt;/p&gt;
&lt;p&gt;I wrote a &lt;code&gt;post-merge&lt;/code&gt; script (name is important), made it executable, and dropped it into this directory.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/sh
# Author: Rafael Cavalcanti

exec &amp;lt;/dev/tty &amp;gt;/dev/tty

readonly changed_files=&quot;$(git diff-tree -r --name-only --no-commit-id ORIG_HEAD HEAD)&quot;

has_changed() {
  echo &quot;$changed_files&quot; | grep --quiet -E &quot;$1&quot;
}

# Update VIM plugins
if has_changed .vimrc; then
  echo &quot;\n[Hook] vimrc changed: Installing/cleaning VIM plugins...&quot;
  # PlugInstall doesn&apos;t proceed with GIT_DIR set (https://github.com/junegunn/vim-plug/issues/506).
  (unset GIT_DIR &amp;amp;&amp;amp; vim +PlugInstall +PlugClean! +qa!)
fi
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Whenever I &lt;code&gt;git pull&lt;/code&gt;, this script will be automatically run by Git. As it is pretty clear, it checks if &lt;code&gt;.vimrc&lt;/code&gt; has changed from the previous &lt;code&gt;HEAD&lt;/code&gt;, and if so, calls &lt;code&gt;PlugInstall&lt;/code&gt; and &lt;code&gt;PlugClean!&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So far so good. However, the hooks directory is not tracked and, thus, not distributed to all installations. Let&apos;s tackle it.&lt;/p&gt;
&lt;h2&gt;Tracking the hook&lt;/h2&gt;
&lt;p&gt;One way to track your hook script is to create a directory in your repo, say &lt;code&gt;git-hooks&lt;/code&gt; and move it there. Then, anytime you clone the repo, you should configure Git to look that directory for hooks: &lt;code&gt;git config core.hooksPath THE_PATH_TO_HOOKS_DIR&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;My dotfiles have a bootstrap script, so I placed this command there, to be run automatically on new clones. For the systems with my dotfiles already installed, I just run the command manually once.&lt;/p&gt;
&lt;p&gt;Done! Now every time I pull my repo and there are changes to my &lt;code&gt;vimrc&lt;/code&gt;, my list of plugins is conveniently checked for new installs and removals.&lt;/p&gt;
</content:encoded></item><item><title>Using Timewarrior on multiple devices</title><link>https://rafaelc.org/posts/using-timewarrior-on-multiple-devices</link><guid isPermaLink="true">https://rafaelc.org/posts/using-timewarrior-on-multiple-devices</guid><pubDate>Thu, 18 Mar 2021 16:06:01 GMT</pubDate><content:encoded>&lt;p&gt;For a few weeks, I&apos;ve been tracking my time with Timewarrior on the desktop. At some point, I missed also having it available on other devices, namely my phone.&lt;/p&gt;
&lt;p&gt;Unlike its sibling Taskwarrior, it lacks a synchronization server, or any official way of using it on multiple machines. So, at first, you are stuck at only using it on one device. However, there is an easy fix for that, and in this post I&apos;ll describe it.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;TL;DR: Install timewarrior on a server and set an alias to run it through SSH.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;My solution&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Pick a Linux host that is always available from any of your devices.&lt;/p&gt;
&lt;p&gt;It could be a Raspberry Pi at your home, a VPS, etc.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install Timewarrior on that host.&lt;/p&gt;
&lt;p&gt;This is where your data will be stored and controlled.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Make sure your devices have SSH access to that host.&lt;/p&gt;
&lt;p&gt;I suggest you to use key-based authentication. Besides being more secure, you don&apos;t have to type your password each time, which will come in handy for this.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;On your devices, add a shell alias for Timewarrior.&lt;/p&gt;
&lt;p&gt;Something such as &lt;code&gt;alias timew=&quot;ssh THE_HOST timew :color&quot;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;:color&lt;/code&gt; option forces color on, since this ssh command won&apos;t allocate a tty.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Make the SSH connection to that host persistent.&lt;/p&gt;
&lt;p&gt;I would get a delay each time I run the &lt;code&gt;timew&lt;/code&gt; alias. I solved this by making the connection persist. This way, only the first invocation would get the delay.&lt;/p&gt;
&lt;p&gt;There are some ways of doing that. I created a directory at &lt;code&gt;~/.ssh/sockets&lt;/code&gt; and wrote this to my &lt;code&gt;~/.ssh/config&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Host THE_HOST
ControlMaster auto
ControlPath  ~/.ssh/sockets/%r@%h-%p
ControlPersist 600
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Done. Now just use Timewarrior normally. Every time you type &lt;code&gt;timew&lt;/code&gt; on your devices, you&apos;ll get to your same, &quot;synchronized&quot; data.&lt;/p&gt;
</content:encoded></item><item><title>Backup your YouTube playlists in JSON with git</title><link>https://rafaelc.org/posts/backup-your-youtube-playlists-in-json-with-git</link><guid isPermaLink="true">https://rafaelc.org/posts/backup-your-youtube-playlists-in-json-with-git</guid><pubDate>Fri, 18 Jun 2021 01:13:49 GMT</pubDate><content:encoded>&lt;p&gt;Backups are important for a number of reasons. As of lately, much of our data has moved to the cloud, so this brings the need to also backup our data from those online services.&lt;/p&gt;
&lt;p&gt;With this in mind, I&apos;ve been building some solutions to periodically backup my most relevant data from the cloud to some system I have control.&lt;/p&gt;
&lt;p&gt;One of the services that I use and consider meaningful to backup is YouTube. My main concern are the playlists. I have many of them, but I don&apos;t care much about the actual video files, so this isn&apos;t about downloading them all. The point is to have some peace of mind about having all the titles and URLs permanently stored somewhere.&lt;/p&gt;
&lt;h2&gt;Getting the JSON for each playlist&lt;/h2&gt;
&lt;p&gt;While &lt;code&gt;yt-dlp&lt;/code&gt; is mostly famous for downloading videos, it has many more capacities. One of them is exactly what we want for our project. With the right options, you can easily get a JSON file with all the videos from a playlist, including their title, channel, URL, description and more. You can use something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ yt-dlp --flat-playlist --dump-json PLAYLIST_URL &amp;gt; output.json
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Just remember that you may need to provide cookies for your YouTube account in case of your playlists being private.&lt;/p&gt;
&lt;p&gt;Then, combine it with a command to get all the playlists, some Bash scripting and in a moment we&apos;ll have all our playlists stored as JSON.&lt;/p&gt;
&lt;h2&gt;Incremental backups&lt;/h2&gt;
&lt;p&gt;So now we have a initial script. But what about next executions?&lt;/p&gt;
&lt;p&gt;At first, I thought of storing each backup in a separate directory. It did work well, but I quickly thought about a better way: version control.&lt;/p&gt;
&lt;p&gt;These are simple plain text files, which are perfect for a git repository. Storing them as such will give us some nice stuff such as automatic deduplication and easier diff.&lt;/p&gt;
&lt;h2&gt;Run periodically&lt;/h2&gt;
&lt;p&gt;After all that, we&apos;re left to schedule the backup, so we can forget about it and let it run automatically. For that, I created a user systemd service that runs weekly.&lt;/p&gt;
&lt;h2&gt;The code&lt;/h2&gt;
&lt;p&gt;If you are interested in the code, &lt;a href=&quot;https://gist.github.com/rc2dev/437e122e5554469a35f10fd893381721&quot;&gt;feel free to take a look&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Why not just use Google Takeout?&lt;/h2&gt;
&lt;p&gt;Instead of writing this script, we could use Google Takeout for our YouTube data. Among other thing, this includes a CSV file containing the URL of each video from your playlists. However...&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;This solution only provides the URLs. I&apos;d like to have more info about each video, such as the title. This makes easier to read and manipulate the data and this info could be invaluable if any of those videos are gone.&lt;/li&gt;
&lt;li&gt;More importantly, Google Takeout is, at best, a semi-automatic process. It can send you an e-mail every 2 months with a link to a page for you to go and download the data. I&apos;d like to completely automate the process of backup.&lt;/li&gt;
&lt;li&gt;Lastly, I&apos;d like to schedule the backup to be more frequently than bi-monthly.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Fixing kdeconnect-cli &quot;no such object path&quot; error</title><link>https://rafaelc.org/posts/fixing-kdeconnect-cli-%22no-such-object-path%22-error</link><guid isPermaLink="true">https://rafaelc.org/posts/fixing-kdeconnect-cli-%22no-such-object-path%22-error</guid><pubDate>Wed, 23 Jun 2021 13:21:23 GMT</pubDate><content:encoded>&lt;p&gt;At some point, &lt;code&gt;kdeconnect-cli&lt;/code&gt; started to throw me this error &lt;code&gt;error: No such object path &apos;/modules/kdeconnect/device/&amp;lt;DEVICE_ID&amp;gt;/share&apos;&lt;/code&gt; when trying to send a file to my phone.&lt;/p&gt;
&lt;p&gt;This seems to mean that the share plugin isn&apos;t enabled on that device. However, I checked it wasn&apos;t the case, by going to the plugin list on the KDE Connect app on the phone. I could fix it by toggling it twice (disabling and re-enabling).&lt;/p&gt;
&lt;p&gt;So, if you are getting a similar error, it&apos;s worth to try this with the respective plugin.&lt;/p&gt;
&lt;p&gt;These are the versions used:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;kdeconnect-cli: 1.4.0&lt;/li&gt;
&lt;li&gt;KDE Connect App (Android): 1.17.0&lt;/li&gt;
&lt;li&gt;Distro: Linux Mint 20.1&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Fix Chromium signing out from Google under Linux Mint 20</title><link>https://rafaelc.org/posts/fix-chromium-signing-out-from-google-under-linux-mint-20</link><guid isPermaLink="true">https://rafaelc.org/posts/fix-chromium-signing-out-from-google-under-linux-mint-20</guid><pubDate>Mon, 12 Jul 2021 22:44:04 GMT</pubDate><content:encoded>&lt;p&gt;Recently, I started experiencing this issue on Chromium under Linux Mint 20.&lt;/p&gt;
&lt;p&gt;Every time I opened Chromium and tried to use a Google service, I would not be logged on them. For example, opening Gmail would show me the login page. I, then, proceeded to login, and everything worked fine until I closed the browser. Opening Chromium again would bring me back to the same problem: I was signed out from Google and prompted to login.&lt;/p&gt;
&lt;p&gt;This only happened to Google, other websites worked fine.&lt;/p&gt;
&lt;p&gt;Clearing the cache, trying different password stores and even deleting every Chromium file on my home directory and starting clean didn&apos;t solve it.&lt;/p&gt;
&lt;p&gt;What did the trick is the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;On Chromium, go to settings.&lt;/li&gt;
&lt;li&gt;Click on &quot;Sync and Google services&quot;.&lt;/li&gt;
&lt;li&gt;Uncheck &quot;Allow Chromium sign-in&quot;.&lt;/li&gt;
&lt;li&gt;Restart the browser.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This issue seems related to the recent announcements around the Google Sync API and &quot;3&lt;sup&gt;rd&lt;/sup&gt; party browsers&quot;. Note that if you try to login to Google through the browser menu it fails silently.&lt;/p&gt;
</content:encoded></item><item><title>Valid certificates for LAN-only websites using HTTP-01 challenge</title><link>https://rafaelc.org/posts/valid-certificates-for-lan-only-websites-using-http-01-challenge</link><guid isPermaLink="true">https://rafaelc.org/posts/valid-certificates-for-lan-only-websites-using-http-01-challenge</guid><pubDate>Tue, 27 Jul 2021 14:18:34 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; You serve some websites exclusively within your network and you own a domain name. You want to enable SSL and that their certificates are recognized out of the box, without manual instalation on each client.&lt;/p&gt;
&lt;p&gt;This rules out a self-signed certificate. A solution may be using Let&apos;s Encrypt with the DNS-01 challenge.&lt;/p&gt;
&lt;p&gt;However, if your DNS provider doesn&apos;t support DNS-01, you may need to use the HTTP-01 challenge. The problem is that, since your websites aren&apos;t publicly available, this challenge will fail.&lt;/p&gt;
&lt;p&gt;In this post, I explain how to run the HTTP-01 challenge under these conditions.&lt;/p&gt;
&lt;h2&gt;The how-to&lt;/h2&gt;
&lt;p&gt;The problem we have here is that your server can&apos;t be contacted from the internet for the HTTP-01 challenge. A way to overcome this is to set up a dummy server to listen in the appropriate place.&lt;/p&gt;
&lt;p&gt;My goal in the next paragraphs is to guide you on how to do it a general way. Then, if needed, you can research a particular step to get more in-depth details for your particular setup.&lt;/p&gt;
&lt;p&gt;To start, I&apos;ll assume you want to setup 2 websites using domain &lt;code&gt;example.com&lt;/code&gt;: &lt;code&gt;subdomain1.example.com&lt;/code&gt; and &lt;code&gt;subdomain2.example.com&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;1. Set up a dummy server&lt;/h3&gt;
&lt;p&gt;First, we&apos;ll set up a web server to be contacted for the challenge. This should be a non-SSL server and listen externally on port 80. You can keep its root empty.&lt;/p&gt;
&lt;p&gt;How exactly you&apos;ll implement it depends on your setup. For example, you could add a new virtual server to the same web server that hosts your LAN websites and set it to listen to a different port. Then, forward that port to external port 80.&lt;/p&gt;
&lt;h3&gt;2. Point your (sub)domains to that IP&lt;/h3&gt;
&lt;p&gt;Configure your domain&apos;s DNS to point &lt;code&gt;subdomain1.example.com&lt;/code&gt; and &lt;code&gt;subdomain2.example.com&lt;/code&gt; to the public IP of the dummy server. If you are configuring many subdomains, consider using &lt;code&gt;CNAME&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;3. Issue the certificate&lt;/h3&gt;
&lt;p&gt;Now with the appropriate Let&apos;s Encrypt tools, issue the certificate using the dummy server. Include all the subdomains you need.&lt;/p&gt;
&lt;p&gt;For example, if you set the root of the dummy server to &lt;code&gt;/var/www/dummy&lt;/code&gt;, using acme.sh you&apos;d run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ acme.sh -d &apos;subdomain1.example.com&apos; -d &apos;subdomain2.example.com&apos; -w &apos;/var/www/dummy&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your dummy server should be contacted and the certificate generated with success. You can then install it normally. For example, on acme.sh you can use &lt;code&gt;--installcert&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;4. Configure the certificate on the actual websites&lt;/h3&gt;
&lt;p&gt;On your web server, configure the actual websites to use the installed certificate.&lt;/p&gt;
&lt;p&gt;Set them to listen on the https port (443) and their &lt;code&gt;server_name&lt;/code&gt; to &lt;code&gt;subdomain1.example.com&lt;/code&gt; or &lt;code&gt;subdomain2.example.com&lt;/code&gt;, accordingly.&lt;/p&gt;
&lt;h3&gt;5. Set split-horizon DNS&lt;/h3&gt;
&lt;p&gt;By now, your real websites are not reachable from the internet, as you wanted. However, they aren&apos;t available for your LAN either.&lt;/p&gt;
&lt;p&gt;To fix this, we need to tell the devices on our network that &lt;code&gt;(subdomain1|subdomain2).example.com&lt;/code&gt; aren&apos;t available on that IP we set on the public DNS, but on the appropriate internal IP.&lt;/p&gt;
&lt;p&gt;Two ways of doing that are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Edit the &lt;code&gt;hosts&lt;/code&gt; file on the devices you want.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set a DNS on your LAN that resolves those entries.&lt;/p&gt;
&lt;p&gt;This can be done on a capable router or on a small device such as a Raspberry Pi.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;6. Test&lt;/h3&gt;
&lt;p&gt;Enter &lt;code&gt;(subdomain1|subdomain2).example.com&lt;/code&gt; while on your LAN and you should access your websites securely with SSL. Exit your LAN and they&apos;ll not be available.&lt;/p&gt;
&lt;h2&gt;All done&lt;/h2&gt;
&lt;p&gt;You now have multiple websites that are only available on your network and they have a valid certificate issued by a trusted CA. If you did everything properly, it should also be automatically renewed.&lt;/p&gt;
</content:encoded></item><item><title>Fix Taskwarrior autocompletion on Termux</title><link>https://rafaelc.org/posts/fix-taskwarrior-autocompletion-on-termux</link><guid isPermaLink="true">https://rafaelc.org/posts/fix-taskwarrior-autocompletion-on-termux</guid><pubDate>Sat, 24 Jul 2021 19:53:30 GMT</pubDate><content:encoded>&lt;p&gt;If you use Taskwarrior on Termux, autocompletion doesn&apos;t work out of the box, be it on Zsh or Bash.&lt;/p&gt;
&lt;p&gt;I found out that the completions are available at &lt;code&gt;$PREFIX/share/doc/task/scripts&lt;/code&gt;, but absent on the appropriate shell completion directories.&lt;/p&gt;
&lt;p&gt;For zsh, I solved it by linking into zsh&apos;s &lt;code&gt;site-functions&lt;/code&gt; directory (&lt;code&gt;vendor-completions&lt;/code&gt; didn&apos;t do it):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ cd $PREFIX/share/zsh/site-functions
$ ln -s $PREFIX/share/doc/task/scripts/zsh/_task
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now I can press tab and have my list of projects and all the good stuff I&apos;m used to on my desktop. If use Bash, the solution should be analogous.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/termux/termux-packages/issues/7183&quot;&gt;I opened an issue for it.&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Relevant versions&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Taskwarrior 2.5.3&lt;/li&gt;
&lt;li&gt;Termux 0.114&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Fix opening locked app from Termux on MIUI</title><link>https://rafaelc.org/posts/fix-opening-locked-app-from-termux-on-miui</link><guid isPermaLink="true">https://rafaelc.org/posts/fix-opening-locked-app-from-termux-on-miui</guid><pubDate>Sun, 25 Jul 2021 15:47:45 GMT</pubDate><content:encoded>&lt;p&gt;Termux has the handy &lt;code&gt;termux-open&lt;/code&gt; command, which is aliased as &lt;code&gt;xdg-open&lt;/code&gt;. I use it to open markdown notes from Termux on Markor (a markdown editor application), by simply running &lt;code&gt;xdg-open foobar.md&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This stopped working when I set Markor to use MIUI&apos;s &quot;app lock&quot; feature, which forces you to authenticate before opening selected apps. Termux would open Markor normally and the &quot;app lock&quot; screen would ask for my credentials. However, after logging in correctly, I would be brought back to Termux. Manually opening Markor wouldn&apos;t show the file I opened.&lt;/p&gt;
&lt;p&gt;I did some tests and the issue didn&apos;t seem to be on Markor&apos;s end. So I went to check on MIUI&apos;s side.&lt;/p&gt;
&lt;p&gt;MIUI has additional permissions, besides the ones you find on AOSP. In my experience, they often are the cause for that type of weird behaviour, when you ask an app for something and at some point they completely ignore the request. I&apos;m thinking, for example, about some KeePassDX features, which require you to fix those MIUI permissions to work properly.&lt;/p&gt;
&lt;p&gt;I checked that Termux had three MIUI permissions in red:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Show on lock screen&lt;/li&gt;
&lt;li&gt;Display pop-up windows while running in the background&lt;/li&gt;
&lt;li&gt;Display pop-up window&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I authorized all three. Now I&apos;m not sure which one exactly did the trick, but this solved the problem.&lt;/p&gt;
&lt;p&gt;So if you use MIUI and happens to have the same situation, long press Termux icon, tap &quot;app info&quot;, &quot;permissions&quot;, and then toggle some or all of those red permissions.&lt;/p&gt;
&lt;h2&gt;Relevant versions&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;MIUI 12.0.3&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Multi-line annotations on Taskwarrior</title><link>https://rafaelc.org/posts/multi-line-annotations-on-taskwarrior</link><guid isPermaLink="true">https://rafaelc.org/posts/multi-line-annotations-on-taskwarrior</guid><pubDate>Wed, 04 Aug 2021 03:52:28 GMT</pubDate><content:encoded>&lt;p&gt;When I started using Taskwarrior, one of the first things that I missed was having plenty of space for writing my annotations.&lt;/p&gt;
&lt;p&gt;Of course, I could add &lt;code&gt;\n&lt;/code&gt;&apos;s as I wished to break lines, but I wanted a comfier place to write them.&lt;/p&gt;
&lt;p&gt;I quickly stumbled upon &lt;a href=&quot;https://github.com/linuxcaffe/tw-ann-hook/blob/9dc2a0c8a7064cc111c967a66c34958bd4b3418f/twan.sh&quot;&gt;a simple script&lt;/a&gt; that could solve that. It worked like this: you run it with the task number, and your preferred editor opens with a blank screen, ready for your annotation. Now when you save and close it, your task is annotated. Very useful.&lt;/p&gt;
&lt;p&gt;I started to use that script, fixed some minor issues, added features, made it more robust... &lt;a href=&quot;https://gist.github.com/rc2dev/9ad4847a5a6fb829f616529fe7311b91&quot;&gt;You can find the result here.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Some features I added:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It accepts a filter instead of task ID, which may be convenient and more similar to the Taskwarrior interface.&lt;/li&gt;
&lt;li&gt;You can avoid opening the editor if you provide the annotation as an argument, as you would do with Taskwarrior.&lt;/li&gt;
&lt;li&gt;It has some checks to avoid surprises and data loss. For example, if your filter doesn&apos;t return any task, the editor won&apos;t be started. If there is any error when adding your annotation, you won&apos;t lose your text.&lt;/li&gt;
&lt;li&gt;It enables syntax highlighting for markdown in VIM or any editor that checks the file extension.&lt;/li&gt;
&lt;li&gt;It distinguishes between multi-line and one-line annotations, adding a line break to the former.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My workflow is the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I have the script placed on &lt;code&gt;PATH&lt;/code&gt; and I name it &lt;code&gt;tan&lt;/code&gt;. This is an abbreviation for &lt;code&gt;task annotate&lt;/code&gt;. I named this way to emulate my Taskwarrior aliases, which are &lt;code&gt;tad&lt;/code&gt; for &lt;code&gt;task add&lt;/code&gt;, &lt;code&gt;te&lt;/code&gt; for &lt;code&gt;task edit&lt;/code&gt;, etc. So &lt;code&gt;tan&lt;/code&gt; for &lt;code&gt;task annotate&lt;/code&gt;. This makes switching between my aliases and this script seamless.&lt;/li&gt;
&lt;li&gt;Running &lt;code&gt;tan &amp;lt;filter&amp;gt; This is an annotation&lt;/code&gt; will work as expected, as if you used Taskwarrior directly.&lt;/li&gt;
&lt;li&gt;Running &lt;code&gt;tan &amp;lt;filter&amp;gt;&lt;/code&gt; opens your editor (as defined in &lt;code&gt;$EDITOR&lt;/code&gt;). You then are free to write your annotation with the size and break lines that you wish. When saving and exiting the editor, it will be appended to the task.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;However, not everything is glory. This solves the issue of adding large annotations and they are displayed correctly in &lt;code&gt;task info&lt;/code&gt;. However, &lt;code&gt;task edit&lt;/code&gt; can be a confusing view and I find manipulating them somewhat cumbersome.&lt;/p&gt;
</content:encoded></item><item><title>What is termux-dialog and how I use it</title><link>https://rafaelc.org/posts/what-is-termux-dialog-and-how-i-use-it</link><guid isPermaLink="true">https://rafaelc.org/posts/what-is-termux-dialog-and-how-i-use-it</guid><pubDate>Fri, 06 Aug 2021 12:49:33 GMT</pubDate><content:encoded>&lt;p&gt;I have been using Termux for several years, since I came back from iOS to Android. However, only recently I have explored &lt;code&gt;termux-dialog&lt;/code&gt;, and this can be really be a game changer.&lt;/p&gt;
&lt;h2&gt;What is termux-dialog and why it matters&lt;/h2&gt;
&lt;p&gt;Needless to say, with Termux on your phone you can run &lt;em&gt;(almost)&lt;/em&gt; the same scripts from your Linux box, or write new scripts specifically for your mobile device. Termux allows you to interact with your phone in several ways, such as accessing the file system, opening files and URLs in other applications, etc. It&apos;s a very powerful application if you know what you are doing.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;termux-dialog&lt;/code&gt; command makes it even better by providing an easy way to grab user input, with a mobile UI. Run it and, as the name implies, a dialog will pop-up. There are several type of dialogs: text input, lists, time, date, etc.&lt;/p&gt;
&lt;p&gt;I see it as a sort of dmenu for phones (or zenity, kdialog if you prefer floating WMs). It&apos;s a graphical interface for user input. You first provide some options or ask a question, and with the user&apos;s answer you determine what to do next.&lt;/p&gt;
&lt;p&gt;The alternative to &lt;code&gt;termux-dialog&lt;/code&gt; is to grab the input inside the Termux terminal. In this case, you could ease the typing by showing some kind of text or ncurses menu such as &lt;code&gt;select&lt;/code&gt; in your Bash script. I used it in some occasions, and I&apos;ll continue to in those situations when it makes sense to be in the terminal.&lt;/p&gt;
&lt;p&gt;However, if the script doesn&apos;t have any good reason to spawn a terminal on my phone screen, &lt;code&gt;termux-dialog&lt;/code&gt; is the way. I couple it with &lt;code&gt;termux-toast&lt;/code&gt; or &lt;code&gt;termux-notification&lt;/code&gt; to give output to the user, and &lt;code&gt;termux-widget&lt;/code&gt; for having a way of running it without the terminal. This results in a great mobile-like experience. Depending on your goals, you saved yourself from writing an APK or webpage from scratch.&lt;/p&gt;
&lt;h2&gt;How I use it: productivity system&lt;/h2&gt;
&lt;p&gt;I use some scripts for productivity and note-taking that I wrote tailored for my needs. With Termux, I can run them on my phone and still have a nice experience.&lt;/p&gt;
&lt;p&gt;Using &lt;code&gt;termux-widget&lt;/code&gt;, I have a list of actions ready to go. Tapping on it, &lt;code&gt;termux-dialog&lt;/code&gt; will prompt me with whatever information it needs. For example, which command I want to execute, or what text I want to enter. Also, if a markdown file is opened next, I run VIM on my desktop, but on the phone I can run a markdown application, with the aid of &lt;code&gt;termux-open&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A concrete example of this is my task logging. On my desktop I run &lt;code&gt;tl Watered the plants.&lt;/code&gt; and this is added to my list of done tasks for the day. On my phone, I tap &quot;task&quot; on the widget. A popup asks me if I want to add or log a task. I tap log. A text input - with auto-correction - lets me write &quot;Watered the plants.&quot; Done.&lt;/p&gt;
&lt;h2&gt;How I use it: wake up time&lt;/h2&gt;
&lt;p&gt;Another way I use it is to track my wake up time, which I do daily for more than a year.&lt;/p&gt;
&lt;p&gt;I used to keep this data on a spreadsheet, and then generate charts and calculate a couple of statistics. The data was entered mostly from my phone (as I did it just after waking up), and I have done so in three different ways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Manually appending the data at the end of the rows.&lt;/li&gt;
&lt;li&gt;Through a heavily scripted spreadsheet I wrote. This is what I had been happily using for months.&lt;/li&gt;
&lt;li&gt;I also tried Google Forms for a brief while.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My favourite approach was the second and it worked well. However, it still required to open the Google Sheets app, which is too much interface for so little I wanted to do. Yes, I had a shortcut for this specific workbook on my home, but the problem remains.&lt;/p&gt;
&lt;p&gt;These days, following a bunch of changes on my productivity system, I decided to use Termux for logging this data. After putting together a Bash script, it works as the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Press the appropriate option in the Termux widget.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A time picker is shown, brought you by &lt;code&gt;termux-dialog&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I really like that the time picked by default is the current time, so usually I don&apos;t have to do many changes besides pressing OK.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Done! Today&apos;s time was appended to a &lt;code&gt;tsv&lt;/code&gt; file, together with the date. This file is automatically synced to my other devices with Syncthing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;As a bonus, my script also do two checks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If I&apos;m logging for the second time on the same day.&lt;/li&gt;
&lt;li&gt;If I missed yesterday&apos;s logging. In this case, a notification is displayed and tapping it opens the &lt;code&gt;tsv&lt;/code&gt; file so I can handle it.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What about the statistics? Well, this is as easy as importing the &lt;code&gt;tsv&lt;/code&gt; file into my spreadsheet.&lt;/p&gt;
&lt;p&gt;And this is a much better, non-intrusive experience.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I hope you can build some of your ideas using Termux, &lt;code&gt;termux-dialog&lt;/code&gt; and its other tools, and unleash the power of your phone.&lt;/p&gt;
</content:encoded></item><item><title>Sync WM wallpaper with LightDM on Linux Mint</title><link>https://rafaelc.org/posts/sync-wm-wallpaper-with-lightdm-on-linux-mint</link><guid isPermaLink="true">https://rafaelc.org/posts/sync-wm-wallpaper-with-lightdm-on-linux-mint</guid><pubDate>Thu, 21 Oct 2021 14:03:53 GMT</pubDate><content:encoded>&lt;p&gt;Linux Mint uses LightDM GTK greeter for the login screen. It tries to show each user&apos;s wallpaper, and it works well if you stick to the default Cinnamon desktop environment.&lt;/p&gt;
&lt;p&gt;However, I use a standalone window manager (dwm) and my wallpaper is set by Nitrogen. This breaks the feature, unless we take some steps.&lt;/p&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;At least on Mint (and probably other Debian-based distros), the GTK greeter gets the current user wallpaper from AccountsService. You can find the values set for your user under &lt;code&gt;/var/lib/AccountsService/users/$USER&lt;/code&gt;, but, being a D-Bus service, it is better to interface with it through that mechanism.&lt;/p&gt;
&lt;p&gt;To set the LightDM background to &lt;code&gt;$bg_path&lt;/code&gt;, we can run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ dbus-send \
  --print-reply \
  --system \
  --dest=org.freedesktop.Accounts \
  /org/freedesktop/Accounts/User$(id -u) \
  org.freedesktop.DBus.Properties.Set \
  string:org.freedesktop.DisplayManager.AccountsService \
  string:BackgroundFile \
  variant:string:&quot;$bg_path&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We want this to run whenever we change the wallpaper, so it gets automatically synced. So, &lt;a href=&quot;https://gist.github.com/rc2dev/d45379a7beb2540af04524ebea77c555&quot;&gt;I wrote a wrapper script&lt;/a&gt; that calls this command after I exit Nitrogen.&lt;/p&gt;
&lt;p&gt;Done. Now my login screen always shows my current dwm wallpaper. Of course, make sure that LightDM has read access to your wallpaper file.&lt;/p&gt;
&lt;h2&gt;Relevant versions&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Linux Mint 20.2&lt;/li&gt;
&lt;li&gt;dbus 1.12.16-2&lt;/li&gt;
&lt;li&gt;lightDM 1.30.0&lt;/li&gt;
&lt;li&gt;lightdm-gtk-greeter 2.0.6&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Fix choco-package-list-backup not saving to network</title><link>https://rafaelc.org/posts/fix-choco-package-list-backup-not-saving-to-network</link><guid isPermaLink="true">https://rafaelc.org/posts/fix-choco-package-list-backup-not-saving-to-network</guid><pubDate>Fri, 18 Feb 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;On my &lt;em&gt;(barely used)&lt;/em&gt; Windows box, I wanted to set a backup of the installed Chocolatey packages. The &lt;code&gt;choco-package-list-backup&lt;/code&gt; tool seems to be the way to go. So I installed it and set the backup path to a mapped network drive.&lt;/p&gt;
&lt;p&gt;It didn&apos;t work. Picking different folders under &lt;code&gt;C:\&lt;/code&gt; went fine, but the backup never could be saved to the network location.&lt;/p&gt;
&lt;p&gt;So what was going on?&lt;/p&gt;
&lt;p&gt;It turns out that &lt;code&gt;choco-package-list-backup&lt;/code&gt; schedules itself to run as administrator. Plus, as Chocolatey needs to be run on an elevated terminal, I also run this tool interactively with such privileges. However, on Windows, by default network drives are inaccessible to privileged applications.&lt;/p&gt;
&lt;p&gt;So how to solve this? I did some quick tests and the backup tool run fine as a normal user. So you have two choices:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;change the scheduled task to run without privileges, and always run &lt;code&gt;choco-package-list-backup&lt;/code&gt; this way;&lt;/li&gt;
&lt;li&gt;or allow privileged programs to access your mapped drives.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For the later, &lt;a href=&quot;https://winaero.com/enable-the-access-to-network-drives-from-elevated-apps-running-as-administrator/&quot;&gt;this short guide&lt;/a&gt; does the trick. It instructs you to create a &lt;code&gt;DWORD&lt;/code&gt; key called &lt;code&gt;EnableLinkedConnections&lt;/code&gt; under &lt;code&gt;HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System&lt;/code&gt; and set it to &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Relevant versions&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;choco-package-list-backup 2022.02.06&lt;/li&gt;
&lt;li&gt;Chocolatey 0.12.1&lt;/li&gt;
&lt;li&gt;Windows 11 21H2&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Copying previous commands with fzf and zsh</title><link>https://rafaelc.org/posts/copying-previous-commands-with-fzf-and-zsh</link><guid isPermaLink="true">https://rafaelc.org/posts/copying-previous-commands-with-fzf-and-zsh</guid><pubDate>Thu, 14 Jul 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Sometimes I want to copy a command I previously typed in my shell to the clipboard. It may be for documentation, note-taking, writing a script, setting up an Ansible playbook, sending to someone… You name it.&lt;/p&gt;
&lt;p&gt;While I could always search the terminal window for it and select it, I have set up an alias that makes things much easier and less error-prone:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;alias hy=&quot;
  fc -ln 0 |
  awk &apos;!a[\$0]++&apos; |
  fzf --tac --multi --header &apos;Copy history&apos; |
  xsel --clipboard
&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will perform the task silently. If you prefer a confirmation message after each copy, a clean approach is to convert the alias into a function like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hy() {
  local -r entry=&quot;$(fc -ln 0 | awk &apos;!a[$0]++&apos; | fzf --tac --multi --header &apos;Copy history&apos;)&quot;
  [[ -n &quot;$entry&quot; ]] &amp;amp;&amp;amp; xsel --clipboard &amp;lt;&amp;lt;&amp;lt; &quot;$entry&quot; &amp;gt;/dev/null &amp;amp;&amp;amp; echo &quot;Copied to clipboard.&quot; &amp;gt;&amp;amp;2
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Until now, we assumed the system was running on X11. Now, let&apos;s make it work on Wayland too:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hy() {
  local copy_cmd
  case &quot;$XDG_SESSION_TYPE&quot; in
    x11)     copy_cmd=&quot;xsel --clipboard&quot; ;;
    wayland) copy_cmd=&quot;wl-copy&quot; ;;
    *) echo &quot;Session type not detected.&quot;; return ;;
  esac

  local -r entry=&quot;$(fc -ln 0 | awk &apos;!a[$0]++&apos; | fzf --tac --multi --header &apos;Copy history&apos;)&quot;
  [[ -n &quot;$entry&quot; ]] &amp;amp;&amp;amp; eval $copy_cmd &amp;lt;&amp;lt;&amp;lt; &quot;$entry&quot; &amp;gt;/dev/null &amp;amp;&amp;amp; echo &quot;Copied to clipboard.&quot; &amp;gt;&amp;amp;2
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;In action&lt;/h2&gt;
&lt;figure&gt;
    
      
      Your browser does not support the video tag.
    
    &lt;figcaption&gt;The alias in action.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Typing &lt;code&gt;hy&lt;/code&gt; (short for &quot;history yank&quot;) brings fzf with a list of your most recent commands. You can browse them using the keyboard or perform a fuzzy search. Pressing &lt;code&gt;Enter&lt;/code&gt; copies the selected command to the clipboard.&lt;/p&gt;
&lt;p&gt;Even better, if you enable multi-selection on fzf, you can select multiple commands using &lt;code&gt;Tab&lt;/code&gt;. They will be copied to the clipboard one per line.&lt;/p&gt;
&lt;h2&gt;The how&lt;/h2&gt;
&lt;p&gt;Explaining each part:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;fc -ln 0&lt;/code&gt;: On zsh, this returns the entire shell history, one command per line. If you use Bash or another shell, just replace it with the equivalent command.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;awk &apos;!a[\$0]++&apos;&lt;/code&gt;: The history is piped to awk, which removes duplicate entries. Without this cleaning, fzf may display multiple repeated lines, which can be annoying.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fzf --tac --header &apos;Copy history&apos;&lt;/code&gt;: The result is piped to fzf. The &lt;code&gt;--tac&lt;/code&gt; argument reverses the order (as we want the last commands to be at the top), while &lt;code&gt;--multi&lt;/code&gt; allows multiple selection. We also add a prompt message using &lt;code&gt;--header&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;wl-copy&lt;/code&gt; (on Wayland) or &lt;code&gt;xsel --clipboard&lt;/code&gt; (on X11): Finally, the commands selected are copied to the clipboard.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Needless to say, be sure to have the mentioned tools installed on your system for this to work.&lt;/p&gt;
</content:encoded></item><item><title>Mounting SMB1 shares after kernel 5.15</title><link>https://rafaelc.org/posts/mounting-smb1-shares-after-kernel-5.15</link><guid isPermaLink="true">https://rafaelc.org/posts/mounting-smb1-shares-after-kernel-5.15</guid><pubDate>Tue, 29 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Linux kernel 5.15 &lt;a href=&quot;https://wiki.samba.org/index.php/LinuxCIFSKernel#5.15_kernel_.2825_patches.29&quot;&gt;dropped support for NTLMv1&lt;/a&gt;, a weaker authentication algorithm.&lt;/p&gt;
&lt;p&gt;As a consequence, if you have old hardware that only supports SMB1 (such as Apple’s Airport Time Capsule), you may have trouble mounting them on recent distributions like Ubuntu 22.04.&lt;/p&gt;
&lt;p&gt;Some workarounds are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Downgrading or patching the kernel.&lt;/li&gt;
&lt;li&gt;Using an extra device between the SMB1 hardware and your client to do the mount.&lt;/li&gt;
&lt;li&gt;Switching from SMB to another protocol, if the device supports it. &lt;a href=&quot;/posts/mounting-airport-time-capsule-on-linux-in-2025/&quot;&gt;For example, the Time Capsule is reachable via AFP.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;An easy path, however, is to enable guest mode on that hardware, if available. Then, just mount it on your client with something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo mount -t cifs -o guest,vers=1.0 //SERVER/PATH /mnt/path
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sure, you lose the password &quot;protection&quot;, and you may be left with only one share. However, taking in account that the security for SMB1 and NTLMv1 is broken for a long time, that’s not much of a loss.&lt;/p&gt;
&lt;p&gt;More information is available &lt;a href=&quot;https://bugzilla.kernel.org/show_bug.cgi?id=215375&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Point a subdomain to an EC2 instance without elastic IP</title><link>https://rafaelc.org/posts/point-a-subdomain-to-an-ec2-instance-without-elastic-ip</link><guid isPermaLink="true">https://rafaelc.org/posts/point-a-subdomain-to-an-ec2-instance-without-elastic-ip</guid><pubDate>Mon, 29 May 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The IP address of an AWS EC2 instance is dynamically assigned when it launches. Allocating an elastic IP to the instance prevents the address from changing, but you might be in a situation in which you can&apos;t or don&apos;t want to use this service. In this case, it might be handy to have a name that always points to the current IP of the instance.&lt;/p&gt;
&lt;p&gt;What we are looking for is dynamic DNS, and you only need two things: a DNS provider that supports it and a client to periodically send it the current public IP of the system. One of such clients is &lt;code&gt;ddclient&lt;/code&gt;, which is compatible with many dynamic DNS providers and is packaged in many Linux distributions. I&apos;ve been using it with Google Domains for years, and it works well.&lt;/p&gt;
&lt;p&gt;Install it and configure it on your EC2 instance. On Ubuntu and Debian, it is just a &lt;code&gt;sudo apt install ddclient&lt;/code&gt; away, and you&apos;ll be prompted with an easy setup process. You&apos;ll need to configure a name on your DNS provider and get the necessary credentials to input in the wizard.&lt;/p&gt;
&lt;p&gt;And, simple as that, you have a subdomain that always points to your instance. Obviously, after launching the EC2 instance you&apos;ll need to wait for DNS propagation. In my experience it takes a few minutes.&lt;/p&gt;
&lt;p&gt;Oh, and of course, you can use &lt;code&gt;ddclient&lt;/code&gt; not only on AWS EC2, but on any Linux system with a dynamic public IP address.&lt;/p&gt;
</content:encoded></item><item><title>Debian package for afpfs-ng</title><link>https://rafaelc.org/posts/debian-package-for-afpfs-ng</link><guid isPermaLink="true">https://rafaelc.org/posts/debian-package-for-afpfs-ng</guid><pubDate>Sat, 20 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;code&gt;afpfs-ng&lt;/code&gt; is a client for the Apple Filing Protocol. It is useful, for example, to mount AirPort Time Capsule shares &lt;a href=&quot;/posts/mounting-airport-time-capsule-on-linux-in-2025&quot;&gt;(more on it in this post)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Unfortunatelly, Debian no longer packages it, so I set up a personal repository to easily build and install it. You can find it here: &lt;a href=&quot;https://github.com/rc2dev/afpfs-ng-deb&quot;&gt;afpfs-ng-deb&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>ThinkPad X220&apos;s touchpad and the 90W charger</title><link>https://rafaelc.org/posts/thinkpad-x220&apos;s-touchpad-and-the-90w-charger</link><guid isPermaLink="true">https://rafaelc.org/posts/thinkpad-x220&apos;s-touchpad-and-the-90w-charger</guid><pubDate>Tue, 08 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In this post, I&apos;ll describe my experience replacing my stock 65W charger for a 90W charger on a Lenovo ThinkPad X220 and the unintended consequences for the touchpad behaviour. I&apos;ll report what I&apos;ve tried and which solutions and workarounds I&apos;ve found.&lt;/p&gt;
&lt;h2&gt;The replacement and the issue&lt;/h2&gt;
&lt;p&gt;When my ThinkPad X220&apos;s charger stopped working, I bought a new one. My former charger was 65W, but this was 90W.&lt;/p&gt;
&lt;p&gt;I plugged the new charger and powered on the machine. Everything seemed fine, except the cursor when using the touchpad. If you put your finger on the touchpad, moving it or not, the cursor would start moving erratically around the original position.&lt;/p&gt;
&lt;p&gt;Removing the system from AC power would make the issue go away, which meant that the charger was involved.&lt;/p&gt;
&lt;p&gt;My system at that time was Fedora 38. Booting into Windows or other distros also showed the problem.&lt;/p&gt;
&lt;h2&gt;Updating the firmware&lt;/h2&gt;
&lt;p&gt;This issue is documented &lt;a href=&quot;https://forums.lenovo.com/t5/ThinkPad-X-Series-Laptops/X220-Touchpad-jumpy-response-when-used-with-90w-slim-AC-adapter/ta-p/766543&quot;&gt;here&lt;/a&gt; and, as stated in the link, a firmware update was released to solve it. While the file attachment on that page doesn&apos;t work anymore, you can find it for download &lt;a href=&quot;https://x220.mcdonnelltech.com/resources/&quot;&gt;on this excellent page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&apos;m a Linux user. I saw some reports of incompatibility between that new firmware and MacOS, but nothing very concrete about Linux. I couldn&apos;t be sure that it would solve the issue, but I decided to try.&lt;/p&gt;
&lt;p&gt;The update software only works on Windows, so I installed a fresh copy of Windows and applied the update.&lt;/p&gt;
&lt;h2&gt;Windows: Results of the new firmware&lt;/h2&gt;
&lt;p&gt;As I said, I&apos;m not a Windows user, so I won&apos;t review the results of the update on Windows comprehensively. However, from what I&apos;ve seen on my spare interactions with that OS on the X220, after the firmware update:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Windows 11 installation media still has the issue.&lt;/li&gt;
&lt;li&gt;A fresh Windows 11 install still has the issue.&lt;/li&gt;
&lt;li&gt;On a fresh Windows 11 system, after installing the Synaptics driver from Lenovo&apos;s support page, the issue seems gone. However, the cursor now moves much slower.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Linux: libinput flag&lt;/h2&gt;
&lt;p&gt;On Linux, having tested different distros, the problem persists after the firmware update.&lt;/p&gt;
&lt;p&gt;The ArchWiki states that &lt;a href=&quot;https://wiki.archlinux.org/title/Lenovo_ThinkPad_X220#Touchpad&quot;&gt;you can set a flag&lt;/a&gt; for &lt;code&gt;libinput&lt;/code&gt; to address issues on firmware 8.1 (the version to which we updated).&lt;/p&gt;
&lt;p&gt;I have applied that flag both on Fedora 38 as on Debian Bookworm. My experience is that, while it brings some improvement, the issue is still present.&lt;/p&gt;
&lt;h2&gt;Linux: Synaptics&lt;/h2&gt;
&lt;p&gt;From what I&apos;ve heard, after applying firmware 8.1, the touchpad on X220 is similar to the one on X230.&lt;/p&gt;
&lt;p&gt;So I tried &lt;a href=&quot;https://wiki.archlinux.org/title/Lenovo_ThinkPad_X230#Touchpad&quot;&gt;a fix for the later available at ArchWiki&lt;/a&gt;. It consists in switching to the legacy Synaptics driver and doing some configuration on it.&lt;/p&gt;
&lt;p&gt;The package for Synaptics will vary depending on your distro. On Debian, you can install it with &lt;code&gt;sudo apt install xserver-xorg-input-synaptics&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Then, drop the configuration from ArchWiki at &lt;code&gt;/etc/X11/xorg.conf.d/30-touchpad.conf&lt;/code&gt;. I&apos;ll paste it here for reference:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Section &quot;InputClass&quot;
    Identifier &quot;touchpad&quot;
    MatchProduct &quot;SynPS/2 Synaptics TouchPad&quot;
    Driver &quot;synaptics&quot;

    # Disable two fingers right mouse click
    Option &quot;TapButton2&quot; &quot;0&quot;

    # fix touchpad resolution
    Option &quot;VertResolution&quot; &quot;100&quot;
    Option &quot;HorizResolution&quot; &quot;65&quot;

    # disable synaptics driver pointer acceleration
    Option &quot;MinSpeed&quot; &quot;1&quot;
    Option &quot;MaxSpeed&quot; &quot;1&quot;

    # tweak the X-server pointer acceleration
    Option &quot;AccelerationProfile&quot; &quot;2&quot;
    Option &quot;AdaptiveDeceleration&quot; &quot;16&quot;
    Option &quot;ConstantDeceleration&quot; &quot;16&quot;
    Option &quot;VelocityScale&quot; &quot;20&quot;
    Option &quot;AccelerationNumerator&quot; &quot;30&quot;
    Option &quot;AccelerationDenominator&quot; &quot;10&quot;
    Option &quot;AccelerationThreshold&quot; &quot;10&quot;

    Option &quot;HorizHysteresis&quot; &quot;100&quot;
    Option &quot;VertHysteresis&quot; &quot;100&quot;

    # fix touchpad scroll speed
    Option &quot;VertScrollDelta&quot; &quot;500&quot;
    Option &quot;HorizScrollDelta&quot; &quot;500&quot;
EndSection
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For my taste, the speed of the cursor is a bit slow. I solved it by bumping &lt;code&gt;VelocityScale&lt;/code&gt; a bit. Also, if you want to enable tapping to click add:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Option &quot;TapButton1&quot; &quot;1&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now restart the Xorg server and voilà. On my Debian Bookworm system, the &quot;shaky&quot; behaviour is gone.&lt;/p&gt;
</content:encoded></item><item><title>Automatically switch to dark theme with Redshift hooks</title><link>https://rafaelc.org/posts/automatically-switch-to-dark-theme-with-redshift-hooks</link><guid isPermaLink="true">https://rafaelc.org/posts/automatically-switch-to-dark-theme-with-redshift-hooks</guid><pubDate>Fri, 24 Mar 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Sometimes, I switch between a dark and a light theme on my desktop. Long before the big DE&apos;s (or other operating systems) offered that feature via a simple button, I wrote a script to automate that task.&lt;/p&gt;
&lt;p&gt;That script is now 6 years old and has evolved together with my desktop use. I&apos;ve updated it with my different setups, and at some point it supported more than 5 DE&apos;s. I have it associated to a keybinding, so the toggle is easy and fast on my dwm desktop.&lt;/p&gt;
&lt;p&gt;For a long time, I&apos;ve been mostly using the dark theme. However, lately I&apos;ve found myself repeatedly switching to the light theme during daytime.&lt;/p&gt;
&lt;p&gt;Therefore, my goal is to automate that daylight/night toggling, while keeping the possibility of manual toggling at any time.&lt;/p&gt;
&lt;h2&gt;The how&lt;/h2&gt;
&lt;p&gt;To implement that automation, the first most obvious tools we can think of using are cron or systemd-timers.&lt;/p&gt;
&lt;p&gt;There is also a couple of projects that specifically aim to solve that. They ask you to run their daemon, which tries to switch your theme at sunrise/sunset. However, they only do it successfully on some setups (not mine), and so I&apos;m left to provide them a custom script to execute at that time.&lt;/p&gt;
&lt;p&gt;For me, however, the better alternative is using Redshift hooks.&lt;/p&gt;
&lt;p&gt;Redshift is already always running on my desktop and knows when it&apos;s night or day. It has support for hooks, which means you can drop a script in a directory, and it will be executed when some events happen, including sunset and sunrise.&lt;/p&gt;
&lt;p&gt;It&apos;s an elegant solution and it doesn&apos;t require any more daemons running. This also works on Gammastep, which is a Redshift fork, just by changing the configuration directory accordingly.&lt;/p&gt;
&lt;p&gt;If you need some inspiration, here&apos;s my setup:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/rc2dev/dotfiles/blob/master/.config/gammastep/hooks/theme-mode-hook.sh&quot;&gt;Hook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/rc2dev/dotfiles/blob/master/.local/bin/theme-mode-xsettingsd&quot;&gt;Script to set light/dark themes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Mounting AirPort Time Capsule on Linux in 2025</title><link>https://rafaelc.org/posts/mounting-airport-time-capsule-on-linux-in-2025</link><guid isPermaLink="true">https://rafaelc.org/posts/mounting-airport-time-capsule-on-linux-in-2025</guid><pubDate>Sun, 21 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Apple AirPort Time Capsule provides two protocols to access its shares:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SMB1&lt;/li&gt;
&lt;li&gt;Apple Filing Protocol&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this post I&apos;ll briefly review the status of both methods on Linux in 2025.&lt;/p&gt;
&lt;h2&gt;SMB1&lt;/h2&gt;
&lt;p&gt;SMB1 support has been partially dropped from the kernel since 5.15, yet you may still use it with some workarounds. &lt;a href=&quot;/posts/mounting-smb1-shares-after-kernel-5.15&quot;&gt;More details in this post&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Apple Filing Protocol&lt;/h2&gt;
&lt;p&gt;AFP shares can be mounted using &lt;code&gt;afpfs-ng&lt;/code&gt;. The project was archived in 2020, but still works for my use case.&lt;/p&gt;
&lt;p&gt;On ArchLinux you can install it with: &lt;code&gt;sudo pacman -S afpfs-ng&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you run Debian, the package is no longer on the repositories. &lt;a href=&quot;/posts/debian-package-for-afpfs-ng&quot;&gt;This post provides a solution&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item></channel></rss>