GeistHaus
log in · sign up

https://xrvs.net/index.xml

atom
54 posts
Polling state
Status active
Last polled May 18, 2026 21:52 UTC
Next poll May 19, 2026 21:11 UTC
Poll interval 86400s
ETag W/"69ff3ce2-433f3"
Last-Modified Sat, 09 May 2026 13:55:46 GMT

Posts

2026 April summary
Show full content

I have been delaying writing last month's log for about a week. As it was with the month before, last month was a boring and wretched month.

I have finished another book: A Study in Scarlet. I have finished its review, but since the whole reading section is under reconstruction, it's not published yet.

I am starting to work on pensiv again. Not progressing as much as I liked. I thought the holiday would leave me some time to work on it, but I spent it on a family vacation instead (which I'm not complaining about). I keep thinking of ideas to restructuring it, but when I'm actually at my computer, it's hard to write anything down.

Comic by David Revoy, showing Pepper having all colorful imagination when she does housechores, but when she sits at the table to write/draw it down her head is empty
Comic by David Revoy. Licensed under CC-BY 4.0
https://xrvs.net/log2026-05-09
2026 March summary
Show full content

I have been delaying writing last month's log for about a week. Mostly because last month was a boring and wretched month.

Like the month before that, I didn't finish any new book. A few comics issues were as far as I went.

Also like the month before that, I didn't have energy for any coding projects. I had intended to write a script to visualize my sleep lengths better, but I didn't need that to know my sleeping habit has gone haywire. I should rectify that.

As for artistic project, I started conlanging again, for fun. There is no intention of integrating it into any conworld now. I am currently trying out East Asian languages' features, particularly those of Japanese and Korean, rather than Indo-European as I used to do before. This one has no tone, as I find that trying tones other than the ones I already know are quite tricky, not to mention that it's hard to type. I'm not sure whether to publish this one yet, but I think I will write a pensiv plugin to build it as a website anyways.

https://xrvs.net/log2026-04-07
2026 February summary
Show full content

Last log was last week so this is more of a week summary.

While I haven't completely fixed my mindless scrolling habit, I have managed to write my diary entry every day. I didn't expect it to be too thoughtful, but it did help me to be mindful of what happened, and notice what I did right or wrong.

I still haven't started reading new book since the end of January, nor have I finished my reviews of the old books.

As for personal projects, I haven't had the mental energy left for it. During a after-Tết meeting, my boss has mentioned that side projects are good actually, as it is more interesting stuff and helps develop new skills1; that I agree with, so I assume that's a nod that I can do my personal projects as long as all the dayjob tasks are done. Alas, they're never done, and I'm so drained I don't even want to touch code after work, even if that's something I want to work on. I think in the next month, I'll get back to drawing, which is something that isn't related to coding, until I recover and find back the energy to work on free software.


Footnotes
  1. The “skills” he implies is using LLM; that I don't agree with

https://xrvs.net/log2026-03-01
Log my life & my goals for 2026/Bính Ngọ
Show full content
Logging

It may be a well-known trivia, but if you're one of today's lucky 10000: “blog” is short for “web log”. Some kind of log you put on the web.

Log is a collection of records of what happened. In software engineering, logging is very important, both in the programs themselves (e.g. debug logs, error logs) and outside the programs (changelogs, work logs), because when something goes wrong, you can dig through them to find out what happened. Work logs are also analogous to grimoire — keep one, fellow computer witches.

A daily personal private log is commonly known as a diary or journal. This kind of log often also includes author's feeling about the events that happened in addition to the events themselves. Which is helpful, I suppose, if you want to look back and process and analyze your emotions1. It's also helpful if you feel like you're doing nothing being under the daily grind, but that's often not true. Log your life, and you'll see the progress. If there's no progress? It will irk you so you'll want to make some.

Blogs nowadays are often on the side of opinion essay and lose its original meaning. Not always the case though. For example, this one logs weekly and this one logs monthly. The former is more personal and the latter more work-ish. A lot of the time, blogs are just boring updates, but how are they different from social media updates/posts/statuses/whatever? Friends generally want to know what you're up to from time to time, and I think this is another neat purpose for a public log. It is not tied to any social media, which is more power to you.

My achievements last year

Around the end of last year, in a chat with a friend, who was like me, being gloomy about not achieving anything, which is, as I said above, not true. He revealed to me that he kept a yearly log, because it was easy for him to focus on the negative stuff than the positive achievements. And he did achieve a lot, both professionally and socially. I did not have such log, so I had to dig other stuff to remind myself of what I've accomplished.

I don't include work-related stuff, because my employer's values are so different from my personal values, I don't really consider it personal development. If you know one that aligns better with my values please pitch it to me.

One of the things I'm most proud of last year was that I started a habit of running every week, in addition to cycling to the gym almost every day. Not only that, I also get two friends to run with me. Of course, running for a day then being idles for the rest of the week isn't healthy either, but that's not the case. The two friends ended up being more active in the week as well, which I believe is a result of me encouraging them to run.

As for the software projects, I managed to implement a proof-of-concept for the algorithm of TELEX implementation I'd do for florisboard, but I haven't done the latter. This static site generator I use to create this site is also in usable state2, but I think I'll refactor it further this year. A few changes to my custom client for Akkoma was done, but no new release last year.

I laid out my goal to read 4 books, and I did exactly that: I finished Tous les hommes sont mortels and the Tam Thể trilogy. I haven't finish my writing on the trilogy though.

Goals Logging

Last year was a silent year for this site, so this year I want to change that. I'll do several kinds of logging, with different frequencies:

  • Private log(i.e. diary): every day
  • Web log (public): monthly, summarizing notable personal updates that I'm willing to share publicly, as well as projects if any. This doesn't mean that I will only publish monthly.
  • Reading log: Whenever I finish a book
  • Watching log: Whenever I finish a movie or a TV series

The reading log is currently under reconstruction, and I also have a few unfinished drafts that I don't want to publish yet. As for watching log: it's empty and I removed it for now.

Due to the changed purpose of the “blog”, I'll consider separating the essays into its own section. To be fair, essays are often just logging of your opinions about some events that occurred around that time, so they might stay. Anyways, sorry in advance if I mess up the feeds.

Reading

I plan to read 5 books this year:

  • 2 in Vietnamese (I already finished one)
  • 2 in English
  • 1 in French
Health

During last year, I have a totally wrecked sleeping habits. At one point I reached a terrible state of sleep deprivation that felt like intoxication. I eventually got sober, but then kept staying up late again and only sleep 5-6 hours a day. This year, I want to improve on it. I'll sleep 7 hours daily in June and 8 hours by December.

Projects

I'm really split on whether to work on improving pensiv or florisboard this year. The TELEX implementation would have wider and more meaningful impact, but it'd require setting up an Android development environment, which I really don't have the spoons for. The SSG improvement would mostly just let me write new plugins more easily.

For non-software stuff, I've wanted to get back to drawing and also dip a bit into fiction writing. There's no measures for those, but you might hear updates.

This site

I also just joined envs webring and the hacker webring. Will be looking to join more webrings.

Found a site for pen pals all over the world via fedi. I'll check it and consider registering. Or, you could just send me emails if you want to be pen pals winks.


Footnotes
  1. I'm not good at it, but it's one of the things I've been meaning to do.

  2. As I am writing this, I found out that hugo, the previous site generator, has chosen the slop. This is largely unrelated to my motivation for engine switch, but I consider this a collateral win.

https://xrvs.net/log2026-02-22-log-goals
Moving stuff: Part 2
Show full content

Not as dramatic as my last post, I've been moving my Matrix account as well. The matrix server hosted by envs will soon be closed. I can't remember how long I have been using the instance (seemingly since May 2021, according to log from a rather inactive chat), but it has always been stable and up-to-date. Thanks, envs admin.

I will be exclusively on the friend-hosted instance on loang.net.

One problem remains: What about my chats?

Moving Matrix account.

Unlike the last time when I moved from matrix.org, now I have a number of chats I don't want to lose, especially chats with people outside the techie circle that I managed to invite.

Apparently, I can connect to a service hosted by Element that will move my account, but giving credentials to a corporation is not what I want. Fortunately, having the same concern as I do, someone else already figured out another way and wrote a guide about it. It is also rather simple as well, as long as you're willing to do some manual work. It's basically:

  1. Export, then import your encryption keys to new account (the UI has changed since the guide was published: it's in the “Encryption” setting now)
  2. Invite/Join existing chats
  3. Set your new account to be admin for direct chats
  4. Edit some metadata so they appear as direct chats for the new account
  5. Leave rooms from the old account

Step 1 seemed obvious, but I somehow didn't think of that. Step 4 is not really necessary, but direct chats not shown as such have been annoying me so the guide really helped, and I should rewrite that part here in case the original guide is gone.

Apparently, direct chats are stored as a metadata for the account that can be accessed via the devtools (only available on Element: type /devtools in chat) and select “Explore Account Data”. Open m.direct, and edit the JSON, which looks something like:

{
  "@account_one:example.com": [
    "!room_id:example.com"
  ],
  "@account_n:example.com": [
    "!room_id:example.com"
  ]
}

where each entry is for the account of the person you're talking to in the direct chat, and the list are for the respective room ID. The room ID is displayed right when you open the devtools.

Other ways to contact

I'm always reachable via email (my @loang.net email works now, but @xrvs.net and @disroot.org emails are more reliable) and my fedi accounts. For more secure channels, other than matrix, I'm also on Signal. Overall, I find Signal awesome UX-wise and would recommend any non-tech friends if they ask how to chat with me, but it has a centralization problem, especially when it's hosted in the US. I think I will create a DeltaChat account as well as a contingency plan as well.

https://xrvs.net/log2026-02-14-moving-2
Moving stuff: Part 1
Show full content

So, after my last click-baity post, did you expect some meta post about what I changed? I hope not1.

You expected more book reviews? Not yet.

You expected a new year resolution, because new year? Well, I have to disappoint you.

Sorry, you'll have to hear about my software rant first.

Yes, it's about Firefox. I'm so tired of its bullshit I'm not waiting for the next weekend (when my time is likely sucked up by unexpected events) to write and publish this.

Decay of trust

Around almost a year ago, Firefox made some scandalous update. Promise, huh?

Firefox has been ignoring its users' voice for long. Without looking it up, just from the top of my head: implementing DRM, pushing for Pocket, setting Google as default search engine2 are the most often talked about. I bet there's more.

But I still fooled myself that I can keep up by tweaking the about:config page. Just disable this, enable that. Once I set it, I'm done, right? Apparently not. I also noticed that when I disable some stuff in the settings, in the configs, it's just taking no effect. Damn the placebo config, the illusion of choice.

Yes, the feature was the AI chat bullshit.

Since last year, Firefox, and Mozilla as a whole (and a significant part of open-source software crowd, but that's topic for another rant) chose the slop. They're dead-set on making the web browser an (warning: horrible generated imagery) “AI browser”, insisting this is the future of the web I'm not going to explain what's wrong with that now, as it's a dead horse that has been beaten over and over (but I might write an official stance on this issue later so I can link it elsewhere).

Last week (or so), they introduced AI controls. In a vacuum, this sounds like a decent feature: it allows users to have a choice to opt out of the bullshit. But given its history, I have no reason to trust that it won't revert my setting, or even apply it in the first place. Furthermore, this highlights how much “AI” is shoved in our face without our consent. No, you ask for consent first, not do it and then say, “you could opt out by go to this page and and that button.” This is the drop that overflows the water glass3.

Alternatives

But what alternatives to Firefox are there?

We have Chromium et al, which are made by Google and thus no better.

We have a barely ready Servo. We have Falkon. Several Webkit-based browsers, like GNOME Web aka Epiphany or my favorite go-to for non-JS, history-less browsing, BadWolf. We have text browsers.

But for my primary browser, it needs:

  • to be able to handle JS — for fedi stuff
  • to have uBlock Origin
  • to have bookmarks and history

These criteria leaves only forks of Firefox. The most popular one being LibreWolf, which has all the stuff you don't like disabled by default. However, it also disables a lot of other things, such as cookies, which renders login ephemeral. Of course, it can be disabled, but apparently it also suffers the issues of overwriting settings on update Firefox has, as mentioned by David Revoy, who made the switch last year, then switched back. But this is a trade-off I'm willing to make. In the long run, I don't think this is viable, because it still depends on Firefox after all.

There is an independent Firefox fork called PaleMoon. However, the developers have some, let's say, troublesome past and they don't seem to show remorse about it, so I can't with my conscience endorse it. On the other hand, it's not packaged for most distros, including mine, and only supports a legacy version of uBlock Origin (which might work for most sites to be fair).

Future of software

It seems bleak. Firefox seems to be one of the most prominent example, but it seems to me all major open-source software projects suffer this FOMO and had to jump on this bandwagon. For example, KeepassXC is tainted. No, Bitwarden is not a safe alternative. Even the curl project lead developer, who was so frustrated with slop bug reports/PRs, ended up using GitHub Copilot to review PRs.


Footnotes
  1. There will be a write-up at one point.

  2. For some reason, I was sure it set DuckDuckGo as the default. Maybe one of the distros I used does/did that. Not that DuckDuckGo is trustworthy, but more so than Google for sure.

  3. Or as the anglos say it: the straw that breaks the camel's back.

https://xrvs.net/log2026-02-10-moving-1
Setup nextcloud for local usage
Show full content

I personally use syncthing to sync my files across devices, and it is enough for me. My parents, however, only have one device and don't have the technical knowledge to set up the service. So, I am setting up nextcloud on my LAN, hoping it'll work for them.

This guides is written for my system, which runs Alpine Linux. If you are familiar with Linux, though, you should be able to apply for your system, except for systems that has its own service configuring system, such as NixOS or GNU Guix perhaps.

Install the necessary packages

I am already having postgresql and nginx running on my system, so I'll use them here as opposed to MariaDB and Apache httpd as recommended in the official doc.

sudo apk add nextcloud nextcloud-pgsql nextcloud-initscript
Configure the stuff

Next, I edit the data directory (where files are stored) to my /data/ partition, since the storage is bigger on there. To do this, I edit the config file /etc/nextcloud/config.php:

<?php
$CONFIG = array (
  'datadirectory' => '/data/nextcloud/data',
  'logfile' => '/var/log/nextcloud/nextcloud.log',
...

(I later learned that this can be configured during account setup.)

I also set trusted_domains to include the LAN address of the server:

'trusted_domains' => 
array (
  0 => '127.0.0.1:12345',
  1 => '192.168.0.x:12345',
  2 => '192.168.1.x:12345',
),

I add a postgresql user for nextcloud:

CREATE USER ncloud WITH PASSWORD 'very strong password';
ALTER ROLE ncloud CREATEDB;

Now, I create a nginx config for the service:

server {
        listen 12345;
        root /usr/share/webapps/nextcloud;
        index index.php index.html;
        disable_symlinks off;

        location / {
                try_files $uri $uri/ /index.html;
        }

        location ~ [^/]\.php(/|$) {
                fastcgi_split_path_info ^(.+?\.php)(/.*)$;
                if (!-f $document_root$fastcgi_script_name) {
                        return 404;
                }
                fastcgi_pass unix:/run/nextcloud/fastcgi.sock;
                        fastcgi_index index.php;
                include fastcgi.conf;
        }
        # Help pass nextcloud's configuration checks after install:
        # Per https://docs.nextcloud.com/server/22/admin_manual/issues/general_troubleshooting.html#service-discovery
        location ^~ /.well-known/carddav { return 301 /remote.php/dav/; }
        location ^~ /.well-known/caldav { return 301 /remote.php/dav/; }
        location ^~ /.well-known/webfinger { return 301 /index.php/.well-known/webfinger; }
        location ^~ /.well-known/nodeinfo { return 301 /index.php/.well-known/nodeinfo; }

        client_max_body_size 100m;
}
Initialize the server

After configuring the services, I runs nextcloud and restart nginx:

sudo rc-service nextcloud start
sudo rc-service nginx stop
sudo rc-service nginx start
sudo rc-update add nextcloud

Now, I head to http://localhost:12345 where the server is running. It prompts for admin account and DB credentials. Filling these out, and it will shows recommended apps to install. I don't care about these though, I just need file sync, so I skip this and go to create accounts for me and my parents.

Cleaning up

Since the ncloud user now no longer needs to create another database, it's good practice to remove that privilege therefrom:

ALTER ROLE ncloud NOCREATEDB;
https://xrvs.net/log2024-04-24-setup-nextcloud
Storing nheko credentials as plain text
Show full content

nheko requires a keyring (like GNOME keyring or keypassxc) to store encrypted password, which doesn't necessarily work, or probably you just don't want to use them, in which case you'd want to disable that.

To do this, edit .config/nheko/nheko.conf and add the config:

[General]
run_without_secure_secrets_service=true
https://xrvs.net/notesnheko-plain-creds
Paste command output into vim
Show full content

To paste the command output into vim, type :r!<command>. For example, to paste current date in ISO 8601 into vim, I can type: r!date -I.

https://xrvs.net/notesvim-paste-command
How to cut videos with ffmpeg
Show full content

To cut video with ffmpeg, use the flag -ss for the beginning of the cut and -to for the end of the cut. For example:

ffmpeg -i in.mp4 -ss 00:00:02 -to 00:00:14 out.mp4
https://xrvs.net/notesffmpeg-cut-video
KeepassXC: how to enable desktop integration
Show full content

Go to Setting > Secret Service Integration > Enable KeepassXC Freedesktop.org Secret Service integration.

https://xrvs.net/noteskeepassxc-enable-desktop-integration
Querying data from SQLite with Mayim
Show full content

(No resolution post this year huh? Maybe I'll save for lunar new year.)

So I'm dabbling with Sanic as an asynchronous web framework, where I learned about Mayim, which is like a wrapper for SQL connectors to "hydrate" the data into models to be validated. Additionally, it also helps with separating the SQL queries from the code to call those more elegantly.

Using Mayim is really simple:

  1. Put your SQL queries inside queries/ folder
  2. Declare models for outputs
  3. Write an executor class with methods named the same as the SQL files

These methods will execute the SQL queries and return the result as the model object.

(See their documentation for more info and code samples)

So far so good. I'm trying out with a small project as a playground for this, so I don't want a DBMS, so I'm sticking to SQLite. That turns out to be some (solvable) troubles because there are stuff that's specific to each SQL implementation.

Most of its example include only one table, to keep the query simple. For an actual app, there are usually several tables, with some one-to-many or many-to-many relations. The most complicated query I see in the doc is an one-to-one relation. It was also PostgresQL though, so they're not immediately applicable for my experiment. Argh it requires pydantic, another library for this to work also---I'm expecting to keep the dependencies minimal, but okay.

For this experiment, I have these tables with products, categories, and prices with both one-to-many and many-to-many relations. The schema is defined by the queries below:

CREATE TABLE product (
  id INTEGER PRIMARY KEY,
  name TEXT NOT NULL,
);

CREATE TABLE price (
  id INTEGER PRIMARY KEY,
  product_id INTEGER NOT NULL,
  unit TEXT NOT NULL,
  price INTEGER NOT NULL,
  FOREIGN KEY (product_id) REFERENCES product (id),
);

CREATE TABLE category (
  id INTEGER PRIMARY KEY,
  name INTEGER TEXT UNIQUE NOT NULL
);

CREATE TABLE product_category (
  id INTEGER PRIMARY KEY,
  product_id INTEGER NOT NULL,
  category_id INTEGER NOT NULL,
  FOREIGN KEY (product_id) REFERENCES product (id),
  FOREIGN KEY (category_id) REFERENCES category (id)
);
class Price(BaseModel):
    id: int
    unit: str
    price: int


class Product(BaseModel):
    id: int
    name: str
    prices: list[Price]

Let's try with the one-to-many one first---the product-price relation.

(As for why there are multiple prices for a single product: when one buys in bulk, the price is usually cheaper.)

Normally, one would query it as:

SELECT product.id, product.name, price, unit
FROM product
JOIN price ON product.id = price.product_id

That doesn't work here: we need price as a list of price objects for each product. From the example linked above, we learned that nested object requires turning to JSON, so let's look at SQLite's document for JSON functions. json_group_array() seems to do what we need here, and we can use json_object() to construct the JSON.

SELECT product.id, name, json_group_array(
  json_object('id', price.id, 'price', price, 'unit', unit)
) as prices
FROM product JOIN price
ON product.id = price.product_id
GROUP BY product.id;

This turns out to be simpler than the PostgresQL example!

However, when I ran that:

pydantic_core._pydantic_core.ValidationError: 1 validation error for Product
prices
  Input should be a valid list [type=list_type, input_value='[{"id":1,"price":8000,...}]', input_type=str]
    For further information visit https://errors.pydantic.dev/2.5/v/list_type

It turns out, the SQLite library can only returns string and not JSON. Maybe they should support that? I don't know. But we can solve that ourselves by extending pydantic's BaseModel. To save you from inspecting pydantic's source code, upon instantiation BaseModel takes **data as initialization parameters, and has model_fields attributes to store type constraints that we defined above. This maps from the field name to an object of type FieldInfo, which contains annotation, which is the data type that we need.

Let's customize that to parse the JSON data:

class SQLiteModel(BaseModel):
    """Custom models to parse JSON from SQLite which is returned as string."""
    def __init__(self, /, **data):
        for field, info in self.model_fields.items():
            # To be careful, only try to parse data expected to be list|dict
            if (type(info.annotation) is types.GenericAlias
                    and info.annotation.__origin__ in [list, dict]
                    and type(data[field]) is str):
                data[field] = json.loads(data[field])
            else:
                if type(info.annotation) is types.GenericAlias:
                    print(data[field])
        super().__init__(**data)

That should work now.

Now, let's add categories to the products:

class Product(SQLiteModel):
    id: int
    name: str
    prices: list[Price]
    categories: list[str]

I'm skipping a bit here because categories has only one data field, though I probably should write full objects as for prices.

SELECT product.id, product.name, json_group_array(category.name) as categories,
json_group_array(
  json_object('id', price.id, 'price', price, 'unit', unit)
) as prices
FROM product
JOIN price ON product.id = price.product_id
JOIN product_category ON product.id = product_category.product_id
JOIN category ON category.id = product_category.category_id
GROUP BY product.id;

Running that would result in rows with duplicated categories like:

Product(id=1, name='energy drink',
        prices=[Price(id=1, unit='bottle', price=12000),
                Price(id=2, unit='packets', price=70000)],
        categories=['drink', 'drink']),

This is because I joined three tables at once, and the categories are repeated the same number of the prices of a product. If I wrote a full model for Category, it wouldn't run at all due to duplicated primary key. To solve this, simply add distinct keyword would work.

So, after this, I found writing bare queries for this small project not too complicated and the encountered problems quite trivial to solve, but I expect it to be less so for larger projects, and so appreciate how much ORM simplifies the process. I'll try out some more with this approach, though.

P.S. Contact me for the test data, which is public but I don't want to publish on this blog.

https://xrvs.net/log2024-01-05-mayim-sqlite
Setup backup on external disk
Show full content
Backstory

A few weeks ago (not today, and by the time I published this post it's a few months ago), I fucked up: During irregular disk cleanup, I accidentally removed the config folder!

Fortunately, I "backed up" the config with git, didn't I? 😌

Well yes, but actually no! 🥲 I did commit, but I didn't push. The latest pushed config had been 9 months before this incident.

On top of that, many programs generate unreadable configs, which I prefer not to put into version control, or put sensitive information or large data into .config, which shouldn't be pushed into a remote repository on a server I don't own. For example, nheko puts authentication info there, and after this incident I lost the session with several messages---well, matrix's irregularly regular decryption failure could be expanded into a post on its own, so let's not digress.

And then there are more things to back up than just configs, say, my photos and music. I mirror them between my devices with syncthing, providing redundancy, but syncthing is not backup, and neither is RAID

I should back them up, or I might one day be doomed to repeat the mistake.

Preparation

Despite the bitterness from the partial data loss, I kinda didn't have the spoon to do what I must, so I've been delaying it hitherto.

Hardware

One of the reason I was cleaning up the disk in the first place was that it's start to be filled up, so I bought a large hard drive to store more data and backup. Doesn't seem too safe, I know: if the hard drive gets lost or broken, I'll lose both the main data and the backup, but with a limited budget and space, that's good enough for now. I am planning for some off-site backup.

I got a Seagate One Touch 2 TB1 HDD. It seems to come with builtin backup software, which doesn't support Linux of course, and I likely wouldn't use it even if it did. A USB hub is also needed, as my laptop has a limited number of USB ports.

Formatting

Coming with the hard drive are several files, such as an .exe program to initialize the backup, I suppose. I don't need these, and the disk can be formatted right away after I do full-disk encryption on that.

cryptsetup luksFormat /dev/sdb
cryptsetup open --type luks /dev/sdb extern

No partitioning is needed: while I do seek vegan alternative, I intended to use btrfs as the file system, which can create subvolumes, which can act as separate mounted partitions. I would just reformat the existing partition to btrfs.

mkfs.btrfs -L margarine /dev/mapper/extern

I intended to have three subvolumes, one for big data (not Big Data™) such as movies or music, one for more personal data and another for backup:

mkdir /data
mount /dev/mapper/extern /data
btrfs subvolume create /data/hoard
btrfs subvolume create /data/perso
btrfs subvolume create /data/backup
ACCIDENT!

OK, I'll admit I don't know what the heck went on, but the main user, xarvos, and root were apparently deleted, but not actually. I cannot log in to either account, but they were still listed in the /etc/passwd file. Huh? I cancelled a rebuild, which I have done a dozen times before and nothing went wrong so it should be fine this time, right? Right? (Looking at the laptop Padmely)

Per later investigation, it looks like for some unknown reason, the NixOS configurations were removed mistakenly, and the failure was triggered by a rebuild.

Rescue

Well, it'd be a lie to say I didn't panic. But this is not something not having happened before (translation for people confused by double negatives: similar thing happened before).

The easiest solution is to reinstall the thing (though it will leave me wondering why this happenned). At the time of my NixOS installation, I didn't make use of the service configurations in nix, only declaring those packages and invoke them manually. Additionally, like cnx, I also didn't think of full-disk encryption during installation. So, this is an opportunity to rectify those wrongs

Backing up

Thanks to the available external disk as well as a ready bootable disk (you should always have those, for occasions like these), I can simply back up my whole home directory. I don't do stuff elsewhere, so no needs to back those up.

mkdir /mnt/{int,ext}
mount /dev/sda1 /mnt/int
cryptsetup open --type luks /dev/sdb extern
mount /dev/mapper/extern /mnt/ext
rsync -a /mnt/int/home/xarvos /mnt/ext/backup/manual-$(date)-home-xarvos

This also mean I can do the rescue while documenting this process at the same time 😃.

The bootable USB drive is a NixOS installer, but it's not the latest version, so I burn a newer installer to a second one (so handy), after backing up the second one's content to the disk using similar process.

Reinstallation

OK, it's time to remount. I can install right now with nixos-install --root=/mnt/int, but I want to include the external disk in the same root, so that their hardware configuration can be generated together. Though, before remounting, I should encrypt the main partition.

Previously, I only have two partition---the main one and the swap, but now I would need three, because the /boot can't be encrypted

parted /dev/sda
(parted) mklabel msdos
(parted) mkpart primary 128MB -8GB
(parted) mkpart primary linux-swap -8GB 100%
(parted) mkpart primary 1MB 128MB
(parted) set 3 boot on
mkswap /dev/sda2
swapon /dev/sda2
mkfs.ext4 -L boot /dev/sda3

cryptsetup luksFormat /dev/sda1
cryptsetup open --type luks /dev/sda1 intern
mkfs.btrfs -L pain /dev/mapper/intern

(pain as in French for bread, because of course we spread margarine on bread :wink:)

mount /dev/mapper/intern /mnt
mkdir /mnt/data
mount /dev/mapper/extern /mnt/data

Before generating the config, I'd like to make some more subvolumes. I didn't intend to run with my root on tmpfs like cnx, but I believe it'd be better to have different partitions to be backupped (with snapshots) separately.

btrfs subvolume create /mnt/etc
btrfs subvolume create /mnt/home
btrfs subvolume create /mnt/root
btrfs subvolume create /mnt/var

After that, I copied the configuration to /etc and generated the config.

mkdir /mnt/etc/nixos
cp -r /mnt/data/backup/.../{services,configuration.nix} /mnt/etc/nixos
nixos-generate-config --root /mnt

(Because nixos-generate-config does not overwrite configuration.nix file if it already exists, I can copy the configs there first.)

While in an urgence (there was one day and a half before the holiday period ended and I needed the machine back for work), I still spent the time to rewrite some services, namely syncthing, transmission, and mpd. For example, I set mpd home to /data/hoard

services.mpd = {
  enable = true;
  user = "xarvos";
  dataDir = "/data/hoard/music";
  startWhenNeeded = true;
};

Finally, I ran nixos-install to finish the setup, and sync'ed the backup back home.

Setting up

(About two months later)

After rebuilding the system, I kinda ran out of energy for doing the backup (again), so I postponed the setup to the week after, but then the next week something happened, which delayed it further and further, and since the momentum died, it took quite a while for me to pick up this again.

My initial plan was to use snapper, as I had good experience with it back when I was using openSUSE.

Snapper (HTTP without TLS link, no idea why) is a tool by openSUSE project that helps managing file system snapshots, which was intended for btrfs, but also supports several other file systems. See also its source, the tutorial on opensuse.org, and ArchLinux wiki article for more information. My main use of snapper was pre-post snapshots, which was integrated into zypper, but I have no use of that on NixOS, since I already manage system changes in git, and NixOS also create different boot options on rebuild as well. Timeline snapshot is also great, but it seems a bit overkill for me. Additionally, snapper doesn't seem to provide a way to send snapshots to the external drive, so I'd have to set that up, and it'd probably be harder to get the names correctly.

So, I'd just copy cnx here, which is less thinking

btrfs subvolume create /data/backup/home
today=$(date --iso-8601)
btrfs subvolume snapshot -r /home /home/$today
sync
btrfs send /home/$today | btrfs receive /data/backup/home
sync

Now, I have to setup a cron job to automate backup, as well as deletion. I modified the script a little so running it twice does not accidentally delete last snapshot.

previous=$(find /home/ -maxdepth 1 -regex '.*20[0-9][0-9]-[0-1][0-9]-[0-3][0-9]')
today=$(date --iso-8601)
if [[ "$previous" == "/home/$today" ]]; then
	echo "Today is already backed up"
else
	echo "Running backup"
	btrfs subvolume snapshot -r /home /home/$today
	btrfs send -p $previous /home/$today | btrfs receive /data/backup/home
	btrfs subvolume delete $previous
	sync
fi

Then, I set up the fcron service. Following the example in fcrontab manual, I can use %hours keyword so that this runs once in the day from 9h to 16h:

services.fcron = {
  enable = true;
  systab = "%hours 0 9-16 * * * /path/to/backup.sh"
};

I think this is pretty much it. I will wait till tomorrow to see if the cron will run properly.


Footnotes
  1. That means 1.81 TiB and I feel cheated sometimes.

https://xrvs.net/log2023-10-29-setup-external-backup
Site update
Show full content

As you might have noticed, I have a few updates to the site.

  • Appearance: I'm switching the site to using an experiment "victor" theme, which I base on readable.css
  • Links: I set default language (English) to be at a language prefix, which breaks the links. I'm fixing this soon™ (or not)
  • No social links at the bottom. This is partly because of the theming, but also because I find it breaking on different screen sizes and fonts, so they're now moved to the contact page. Links are retained in <head> for fedi verification to work.
  • Tags and categories are currently hidden, also because of the theming

A big TODO is that when I was making this theme, I was abusing categories for site layout, when the more appropriate layout field is there, so I need to update the theme.

Please report to me if any other breakage is in place.

https://xrvs.net/log2023-09-02-site-update
Cleaning my keyboard
Show full content

I have replaced my keyboard with a mechanical one around July last year, so it's been more than a year, but shamefully I haven't cleaned it properly. The most cleaning I've done is using a tissue paper and rubbing over it. In my defense, it came with a plastic cover, so I thought covering it when not in use is enough, but apparently it isn't. As its keys are getting more and more often stuck, I looked more closely between the keys and noticed dust and even cobweb (I'm not exaggerating it).

my keyboard and its plastic cover

I didn't even get the tool needed to disassemble it for cleaning, so I just got a keypuller last week, along with a switch puller and a brush.

So let's start cleaning this keyboard.

First, I remove all of those keycaps. Hopefully I'm not breaking anything in the process.

my keyboard without keycaps, with a lot of dirt

Afterwards, the keys are put into a bowl of water mixed with handwash. Some guides would recommend mixing a lot of 90° alcohol, while some others say too strong agent can damage the keys, so I prefer to stay safe with it.

keycaps in cleaning bowl

Now, I brush the board gently while holding it upside down to avoid dust sticking in further. The keyboard is significantly cleaner after brushing. Then, I use cotton swabs to clean it more thoroughly with water, and then dry them with a towel.

cleaning keyboard with a cotton swab

Next, I wash the keycaps that have been soaking. The keys are quite clean now. I rinse them with water to ensure no soap is remaining.

And voilà, the keyboard is as clean as new!

the keyboard, now clean

OK, not "as new", but it's much better than before. The cleaning process takes around 1 hour.

Finally, I and dry them completely before reassembling them. Several methods I use to dry faster include:

  • use a towel
  • use a fan
  • human-powered centrifugal force

It takes another hour to dry. I tries to use my memory, but it seems I don't remember all the positions as well as I thought initially, particularly the navigation buttons and the volume controls on the right of the main keys. Fortunately, I already took the pic of the keyboard before disassembling it, so it is not a problem.

https://xrvs.net/log2023-08-28-cleaning-keyboard
Alternative Communities for Reddit on Fediverse
Show full content

You probably know about the recent Reddit API changes to charge the uses. It is nothing of surprise really, just business as usual. This has incapacitated third-party clients and moderation tools. Numerous subreddits have closed temporarily to protest this change. Personally, I don't find this protest to be very meaningful: 1-2 days are not gonna change anything. I believe people who opposed the change will benefit more from running their own communities on free, open platforms, and no, we don't even have to build from scratch, there are already at least three Reddit-like servers on fediverse: Lemmy, kbin and lotide. Their functionalities may not match one-to-one, but they should be decent enough to work. Migrating to other platforms may not change anything for the API situation either, but the point is that one is no longer have to play Reddit's game anymore.

In this post, I'll list some of communities and their counterparts on fediverse. If you know of any that isn't listed, please let me know.

Subreddits Alternative communities Server r/Music Music@beehaw.org Lemmy r/Music Music@narwhal.city lotide r/Music music@kbin.social kbin r/Vietnam vietnam@slrpnk.net Lemmy r/books books@kbin.social kbin r/books books@lemmy.ml Lemmy r/explainlikeimfive ELI5@kbin.social kbin r/food food@beehaw.org Lemmy r/food food@kbin.social kbin r/food food@lemmy.ml Lemmy r/food food@slrpnk.net Lemmy r/food recipes@lemmy.ml Lemmy r/memes memes@lemmy.ml Lemmy r/memes memes@slrpnk.net Lemmy r/piracy piracy@lemmy.dbzer0.com Lemmy r/piracy piracy@lemmy.ml Lemmy r/science science@beehaw.org Lemmy r/science science@kbin.social kbin r/science science@lemmy.ml Lemmy r/worldbuilding worldbuilding@lemmy.ml Lemmy

On a related note, I think these alternative servers would benefit a lot from having great clients as those targeted Reddit. Anyone willing to work on remapping those API endpoints?

https://xrvs.net/log2023-06-12-reddit-alt-communities
SOCKS Proxy via SSH
Show full content

SOCKS (RFC 1928) is a protocol that can be, as said in the RFC itself, used for firewall traversal, or some other types of network blocking.

If you have a remote server that you can SSH to, setting up a SOCKS connection is dead simple:

ssh -D [port] [host]

where [host] is the host name you specified in the SSH config file.

How to get your software to direct its connection through this proxy depends on the program. For example, in Firefox, you have to go to the setting and set it in the network settings---use your server's address and the port you used earlier. In Chromium and similar forks, add --proxy-server="socks5://host:port" to the parameter in the command line. Read more on the instruction for Firefox and Chromium on their respective websites.

https://xrvs.net/notessocks-proxy
Writing a DICT (RFC 2229) server
Show full content

In last few weeks, I've implemented a minimal, barely compliant1 DICT server called ExTra (also stylized ex.tra). The server implements the protocol as described in the linked specification, as well as reading from existing text-databases used by other servers (dictd and GNU dico). I've done this as an exercise for learning Elixir, mainly, and it lacks many features in comparison with the two other existing implementations.

Spec summary

DICT is a simple protocol for looking up dictionaries over TCP/IP. It include a handful (well, more than handful, if you include the optional authentication) commands like MATCH or DEFINE to retrieve entries from a dictionary database. The database format is not defined, so technically, one can make it to work with, say, an SQL database, but it's customary to use the format the reference implementation (dictd) uses.

I largely built this based on Elixir's guide for building a key-value server

Architecture

This diagram shows a very rough and simplified architecture overview of ExTra. Yea, I know, it looks ugly, but organizing a diagram is hard, so I'll describe it in details below.

Architecture overview of ExTra

The processes2 are supervised by the application supervisor, and are respawned once they crash. There are three main processes concerned here:

  • ExTra.Server: This one accepts connections from the dedicated port
  • Task.Supervisor: This one is a supervisor that will spawn new processes that will bind to clients until disconnected.
  • ExTra.Dict is a GenServer that executes commands sent to the server, and reads from dictionaries to generate response. Note that GenServer is not a TCP/IP server.
TCP server

The TCP server is implemented using erlang's :gen_tcp:

{:ok, client} = :gen_tcp.accept(socket)

Here the client is a connection to client. We send and receive data via this process. To read from this until disconnection, we would put it on an infinite loop (acceptor in the diagram). However, that also means the server is locked to that client and cannot accept another---you probably have learned this from a network class implementing an echo server in C. This is where the task supervisor comes in: instead of running directly into the infinite loop, we spawn a Task that does it:

Task.Supervisor.start_child(ExTra.ConnSupervisor, fn -> serve_first(client, host) end)

In the loop, commands are parsed and run, something like ExTra.Command.parse(data) and ExTra.Command.run(commands) The ExTra.Command module then sends these commands to ExTra.Dict GenServer to execute it, something like ExTra.Dict.command(ExTra.Dict, command).

GenServer

The commands are sent to the GenServer and handled by some other modules:

@impl true
def handle_call({:define, dictionary, word}, _from, state) do
  {:reply, ExTra.Dict.Define.define(dictionary, word), state}
end

def handle_call({:match, dictionary, strategy, word}, _from, state) do
  {:reply, ExTra.Dict.Match.match(dictionary, strategy, word), state}
end

This level of abstraction may seems a bit convoluted, but using GenServer here would allow for caching matches and definitions, and separating matches and definitions to a separate modules allow for different search modules depending on config. Not a necessary thing, just for educational purpose.

Matching definitions

The .dict file stores entries as well as metadata as plain text, while the .index file store positions of the entries as:

<entry> <start> <length>

Where <start> and <length> are in quartosexagesimal or base 64. Numeral base 64, not base 64 encoding that is implemented in the standard library. After a few shortening, the conversion is done in less than 20 lines:

def base64num(num) do
  alphabet =
    'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
    |> Stream.with_index()
    |> Enum.into(%{})

  len = String.length(num)

  num
  |> String.to_charlist()
  |> Stream.with_index()
  |> Stream.map(fn {c, i} -> {alphabet[c], len - i - 1} end)
  # Left-shift 6 * power bits is equal to multiply by (2^6)^power, but faster
  |> Stream.map(fn {digit, power} -> Bitwise.bsl(digit, power * 6) end)
  |> Enum.sum()
end

Firstly, I mapped the digits in the alphabet to its respective values, which are also their indices in the char list. The digits in the input string are then mapped to their values based on this map, while their indices are mapped to the power. Finally, these values are powered and summed as the answer.

Upon reading these values, the definition from the dict files can be retrieved as simply as:

# the content that comes before what we need
_ = IO.read(file, start)
# has to `binread` to interpret the UTF-8 encoded characters
IO.binread(file, length)
Further

The full implementation can be found on SourceHut. As this is the first application I've written in Elixir, I'm sure there's a lot of stuff I've written here isn't recommended, so if you have some suggestion for improvement, please send me.

As of writing, there are still several features I'd like to implement that I haven't, such as:

  • Proper response for STATUS and OPTION MIME commands
  • Implement more matching strategies
  • Make uses of the GenServer's state for caching matches

Footnotes
  1. The OPTION MIME command is not yet compliant, actually. It currently is a no-op command while it should check 00-database-mime-header in the file and respond an empty line if not present. However, none of the clients (that is, only dict and dico, as far as I know) does anything with that field, so it doesn't cause any trouble.

  2. To be understood as BEAM processes and not OS processes

https://xrvs.net/log2023-04-11-dict-server
2022 in Review
Show full content

So 2022 has ended, at least for me, at least in UTC. It's time to review what happened in a year and plan for the next. Some in my circle already did that one or two days earlier, but well, I have, uh, reasons to be late.

Last year Work

Last year I officially started a full-time job as a software engineer. Well, I started it before that, but the official contract started in 2022. This means that I had much less time for my personal projects or for free software contribution (not that I did a lot of that before).

I wouldn't exactly say I'm satisfied with this job, but there are certainly benefits I wouldn't have had had I hadn't it, such as a stable income, health insurance, annual health checkups, and two vacations a year. Pretty much that, I think.

There are a few things that stresses me, but I guess I complained enough about them.

Free software

IPWHL didn't last, sadly. The co-maintainer cnx took a hiatus (around April, I think? I have no archive of that), and I in turn had a burnout later around June or July as well (and I felt guilty having never announced that). I also dropped development on Yue and some other projects I started within the year, mostly due to my day job.

I guess the most notable contribution of mine last year was probably packaging: I packaged honk for NixOS and am still packaging dictd for alpine linux (again). Well, that and about six months packaging Python packages for IPWHL too.

In the last two months I have been making a server for managing small shops---mostly stuff like prices and sales of products. It would probably be done in a day if I just use a framework like Django, but then I feel that such framework is an overkill, so I instead decided to implement everything from scratch (well, not everything, I don't rewrite the encryption and database connectors or something like that). It was a bit overwhelming, I think? It shouldn't take that long at all, I suppose I need to rethink my approach. I also decided to learn Vala, a language that compiles to C and is designed for developing with GTK, too. I intend to make some flashcard app as an outcome, and I hope without the network bits it might be somewhat easier to make.

Personal

Last year, I finished reading a second book in French, Ellana, with a higher difficulty than my previous one, Le Petit Prince, I suppose. I have been reading Tous les hommes sont mortels. I'm quite amazed that my French has been still quite fresh enough to read such books with little trouble. Well, I still have to look up a lot of words, but over all, I feel comfortable with that.

Also language related: I got my IELTS test, and to my surprise most are good despite my lack of active practice. I suppose my constant shitposts and occasional rants in English helped.

I did not have much time for conlanging for conlanging, but still a bit for writing. Aside from this blog, I'm slowly rebooting my fictional world for the third time, and [spoiler alert] why not merging them all together?

Lately, I started to have some trouble with figuring my meaning of life and wondering if I'm living an authentic life or I'm just keeping making bad decisions in bad faith.

On my physical health, I started being on a diet plus going to the gym since August. Over all, I felt much better than the previous sedentary life mostly sitting in the office. I also lost some weight, but sadly still have some health problems in the last checkup. Well, more works on that this year then. It was only possible thanks to me working from home, but sadly it is no longer the case.

This year's plan

I have a lot of things going on, so let's keep this short.

It appears that no one other than I am the one to cause my own suffering, I think this year will be the year for me to look inward, and start asking myself the big questions.

Iroh: It's time for you to look inward, and start asking yourself the big
questions

That sounds vague, basically I will do less things that make me unhappy and do more things that makes me happy? Yep, spending more time writing software I use, worldbuilding, making shitposts. I will find time for more physical activities and recaliberating my diet to address my health problems, additionally.

I'm applying for several master's programs abroad, so let's hope that passes, too. On a related note, learning a new language or strengthening my French significantly will also be a goal of mine this year.

https://xrvs.net/log2023-01-01-2022-in-review
Against Duolingo as a language learning platform
Show full content

Up until last week, I would still have recommended Duolingo for language learners for beginner's material, but now I can no longer do so.

It is undeniable that Duolingo is a great source if you want to learn a language (well, great in terms of quantity at least). If you want to start learning a language and don't know where to start, just select a Duolingo course and you will learn from greeting and introduction to political and scientific vocabulary, from alphabet to relative clauses or conditional phrases.

People often criticize Duolingo for having nonsensical sentences you would never use in real life or having only one sentence rather than a conversation, or doesn't have grammar notes. I would even defend Duolingo here, and say they are using it wrongly. Whatever common phrases you would expect to say, "hello", "sorry", "thank you",... are all taught just fine, and the silly ones are actually only a few to let the learners have some fun. Grammar notes are were provided for each lesson, and the silly sentences help you remember the grammar very well. Monologous sentences... well, they solved that, I suppose, with a side effect, but let's leave that for later. The claim that you can't learn a language from solely Duolingo is moot, since you're not supposed to do that; you can't learn from your textbook alone even; you should use it in conjunction with other resources.

Duolingo added ads and ad-free premium tier. It's business as usual; I didn't bat an eye.

Duolingo gamifies the thing further with a leaderboard and ranks? Another silly decision I could ignore.

Duolingo removed discussions. Okay, that's a rather backwards move. Maybe they didn't have enough moderators to handle it, which I doubt since they're expanding their business, but discussion was a very nice feature that allow learners and native speakers to discuss and ask questions on their grammar mistakes or ask for extra learning materials. This was a serious downgrade.

Duolingo made a huge redesign and you can't say no to it. They said you wouldn't lose your progress, but this is a lie: my French and German course progresses which was completed 100% returned to zero (not that I use it for those anymore, but those are on the contrary to what they claimed). Questions now are often dialogues (if you could call 2 sentences so), which probably caused it. Grammar notes are removed, and in their place are these "guide books", which consists of nothing but mere "phrases you will learn in this unit". This is not only an immense downgrade for the learners, but also a huge disrespect to the contributors. They spent time to write those grammar notes, but now they're all gone.

It's not like this happened before. One or two years ago, they added some kind of changes to this, which lost me some progress also, but they didn't go this far. Either way, I should have known better that you cannot trust proprietary platforms to respect you (see: a similar story from someone whose Twitter account is deleted without clear reason). If you don't own it, it will fall out of your hands in one way or another.

So in short, I advises against Duolingo for learning languages because:

  • it has become terribly broken as a language learning platforms, and
  • it is proprietary, it does not respect you

Of course, this extends to online services run on free software as well. Let's say, my Mastodon or Akkoma instance admin can just stop running that server at any moment, intentionally or not, and my data will be lost. The developers can decide to add a new feature that will breaks old data, and I will also lost access to old data. The only solution to this is run the server yourself or back up your data regularly1. However, with a free platform, that data backup can be useful, as it can be used for an another instance of the software. With a proprietary platform, that data will probably just be a big JSON that's very hard to read without a piece of software that no longer exists.

So, my advices to language learners (and to myself as well):

  • use free software, such as Anki, to assist your learning
  • keep local copies of your learning materials, and make sure it's not DRM-encumbered. This includes not only movies, books, music, but also notes and tips from online forums, though the latter might be less valuable.
  • avoid online services which don't allow export/import your data, even if it's run on free software

Basically: own your software, own your data.


Footnotes
  1. I don't do this, though, as I consider social network ephemeral interactions, but for this website it is the case.

https://xrvs.net/log2022-11-06-against-duolingo
Bcrypt hashing time
Show full content
Measurements

This is mere some measurements I make notes for myself, nothing interesting to see here.

I am implementing some authentication, so I was thinking how much cost should I use. The way to determine is to measure how long it takes to hash the password.

Here is the hardware I use:

  • CPU: 11th Gen Intel i5-11400 (12) @ 4.400GHz
  • GPU: Intel RocketLake-S GT1 [UHD Graphics 730]
  • Memory: PNY 8GB

I hash 3 different types of password:

  • short password: silly simple one, short password
  • medium password: 20-character random password: h*uwd'QS0Xozxg5j//+e
  • long password: a passphrase of 20 words: helium policy snort overtone shakable poison corporate curve

Here is the source code, consider it public domain or under CC0 license if you want to use or copy it.

package main
import (
	"fmt"
	"time"
	"golang.org/x/crypto/bcrypt"
)

func main() {
	short := "short pass"
	medium := "h*uwd'QS0Xozxg5j//+e"
	long := "helium policy snort overtone shakable poison corporate curve"
	passwords := []string{short, medium, long}
	for cost := 10; cost <= 20; cost++ {
		fmt.Printf("Cost=%d\t", cost)
		for _, password := range passwords {
			start := time.Now()
			bcrypt.GenerateFromPassword([]byte(password), cost)
			elapsed := time.Since(start)
			fmt.Printf("%s\t", elapsed)
		}
		fmt.Println("")
	}
}
Result Cost short password medium password long password 10 48.672298ms 48.202171ms 48.294102ms 11 96.106021ms 96.47686ms 96.032581ms 12 193.138147ms 192.942441ms 193.234901ms 13 385.703415ms 385.518335ms 385.230291ms 14 774.508302ms 777.079681ms 775.36359ms 15 1.546692701s 1.545946171s 1.565475155s 16 3.092266749s 3.092314898s 3.124079405s 17 6.19333026s 6.177802493s 6.195031959s 18 12.396592375s 12.384743249s 12.407640266s 19 24.824486642s 24.793569567s 24.870305097s 20 50.026644158s 49.712950076s 49.596850425s Comments
  • Hashing time is not dependent on password length (sometimes it can take slightly less time to hash longer password?). If I recall correctly, shorter passwords are padded to required length anyways, so of course there isn't much difference.
  • Time increases exponentially, as it is supposed to be
  • Comparing this with auth0's measurement, this takes slightly less time. It could be due to hardware improvement or implementation (Auth0 uses JavaScript)
https://xrvs.net/log2022-10-23-bcrypt-hashing-time
Why I prefer remote work
Show full content

Having both worked in office and remotely (aka working from home), I find myself preferring the latter much more. In this post, I will detail on how much the benefits of remote work mean to me as well as why I don't view its drawbacks much of problems.

Pros No need to commute

"No need to commute" doesn't sound much; "no need to commute in congestion" would be more telling about the advantage of remote work. Most jobs start and end at around the same time windows and as a result, we have a thing called "rush hours", where we have abnormally high number of people being outside on the road. By simply not having to go outside around these time, I would save up to 3 hours a day! Being stuck around a bunch of cars also contribute to my daily stress. Even when I don't have to travel during these time windows, it would still take me half that time, as most tech companies in my city are located around that place.

Not only do I save time, I also save fuel, thereby saving more money as well as reducing gasoline consumption and pollution. By working from home, I would save more than 3 L of gasoline and avoid exhausting 4 m3 of CO2 per week. Speaking of exhaustion, breathing it continuously in high concentration for 3 hours a day is not exactly healthy.

The saved time and money can be spent on more meaningful things, such as doing daily workouts or sleeping a bit more, both of which I am lacking. This means that remote work is the only way for me to live healthily.

More freedom overall

When I work at home, I can wear whatever I want and no one is gonna judge it. Some workplaces require strict dress code like tugged button-up shirts and shoes. Fortunately, I never worked in a workplace like that, but I am still required to wear uniform on Monday and employee card. There are also implicit dress codes like no slip-slops, shorts or sleeveless shirts in the office, which is no problem if you're at home. There are also no surveillance camera, (unless you're not already covering your laptop's).

I can slack off from time to time: I can do small talk with friends and read new updates from my RSS feed without disapproval eyes from other coworkers---not that this is strictly forbidden, but some people seems to have this weird idea that you have to spend 100% of your time to work. Well, you can't constantly focus for 4 hours long; at least people I know of don't have such attention span. Temporary procrastination can actually help with productivity.

Higher focus

While some people complain about constant distractions from home working, my experience is on the contrary. Each job differs from another, but in one of my job, my coworkers constantly need to ask me for help. By distancing myself from my coworkers constantly asking for support, I can actually focus on my work. This also means that I cannot ask others instantly, but I also find that not being able to do that force me to figure out the problems myself, which in effect help me understand the issue more profoundly.

Less land required

If you're working remotely, you won't use that 3 m2 office lot and a parking lot (assuming you're not using public transport). If your entire company work remotely, you won't need a whole building. Well, you probably still need some office somehow, but from an employer's point of view, shouldn't this be a tremendous advantage?

Cons Under-communication

As many people point out, it can be hard to communicate when you're working remotely. This is very true at the beginning of the forced remote-work (i.e. the COVID pandemic). Since then, video conferencing and other communication methods have improved, but network failure is a problem that always persist. Nonetheless, as I mentioned above, low and unintrusive communication can be and advantage.

Over-communication

To address the above problem, some people resort to over-communicate. We have daily meetings, weekly meetings, fortnightly meetings, monthly meetings---you name it---that are supposed to be 5-minute sync, but can creep up to an hour or even more. This is not to mention random checkup meetings for some urgency. This is the very same problem that would happens with in-office working that I mentioned above.

Work-life balance

Over-communication can in turn lead to work-life imbalance.

There is a reason many people have a self-policy of not bringing work home. This includes not working at home and not discussing work at home. I am such person. Learning from my past internship, I know how distressing being contacted from work can be1, so I don't install any work-related apps on my own devices, and if I do, the notification is always turned off.

Nonetheless, some people seem to have this notion that as you can do work outside your working time, you should (or worse, have to) do it. I had a coworker who brought her laptop home. Yes she did work at home in the evening, and when the temporary remote work period ended, she still kept that habit, even went so far as checking messages/doing meeting on her train. Now, I'm no one to judge her work-life boundaries, but it's extremely annoying that she expected me to be able to contact in some way outside my working time.

Of course, if you have clear boundaries and principles, this shouldn't be a problem.

Security

Network are not to be trusted, especially when it goes out of your local one. From an employer's standpoint, remote work can be undesirable because a malicious employee can leak data to an opponent, which is a valid concerns, except that, they're using cloud services to host their data, communication, and even software substitution. They're already sending data outside to a third party! Not to mention that, there are already plenty of ways to send information out of the office without network, such as copying files to an USB, paper, or just remembering the information.

On that note, blocking internet in a software development environment, unless you're developing internal software and host your own servers, software and documentation, which include, but not limited to:

  • compilers
  • operating systems
  • software package mirrors
  • language and libraries documentation
  • version control server
  • database servers
  • email servers
  • instant messaging servers

that are required in doing software development, or other kind of office works, because as soon as you leak data to the external internet, that false shield has been broken. Well, disclaimer, I'm not a security expert.

Some thoughts

No, I don't think remote work is for everyone, but I insist that allowing it is a must. Obviously, there are jobs where remote work doesn't make sense, but for me particularly, remote work is a step forwards in maintaining a healthy mind (well, at least if you're already principled enough not to bring work home) as well as contributing to environment preservation. If I'm to found a company, I'd encourage my coworkers to work from home, because I believe that's a win for both parties.


Footnotes
  1. While I wasn't directly contacted during my internship, I was in a chat group at work, and them constantly discussing work even after midnight gives me unintended pressure.

https://xrvs.net/log2022-10-15-remote-work
Ending my short-lived experiment with OpenBSD
Show full content

Despite my last post, I am not actually content on this operating system. I believe that I have laid out some of the inconveniences the last time:

  • weird sndio control quirk
  • too limited file access
  • limited file descriptors
  • lack of some packages

I find not being able to use my main messenger software enough a dealbreaker, but last week I have just realized that there are even more dealbreakers:

  • The faulty syncthing has ruined my ebooks.
  • I cannot record audio, so calling is impossible. Yes, I have set kern.audio.record=1. This seems to be some hardware problem; I should have checked carefully before buying, but how could I know?

    On some modern Intel machines, the internal microphone is no longer wired up to the HD Audio device supported by azalia(4).On some modern Intel machines, the internal microphone is no longer wired up to the HD Audio device supported by azalia(4).On some modern Intel machines, the internal microphone is no longer wired up to the HD Audio device supported by azalia(4).

  • Related to calling, sharing screen made firefox crash. I reckon there is some security setting that prevents firefox from capturing screen.

OpenBSD also seems not to make good use of hardware and use up quite a lot of energy compared to other operating systems.

I think I should end this short-lived experience and go back to Linux. I am considering Alpine Linux and Debian GNU/Linux, but given that my hardware don't seem to go quite well, I feel that Debian might be a better choice here.

Meme, Thanos (labelled GNU) saying &quot;Where did that bring you? Back to
me&quot;

https://xrvs.net/log2022-08-15-switch-from-openbsd
Friendship ended with GNU/Linux, OpenBSD is my new best friend
Show full content
Previous experience with openBSD

I tried to revive my old machine a while ago with various Linux distros---Debian, Alpine, Void, but none quite sticks. Graphics wasn't its strong suit. I don't know how it came to be, I was sure 8 years ago it could render stuff just fine. Now even typing on tty feels sluggish. I even remember being able to play Skyrim on it. I decided to turn it to be a server instead.

I don't find Void to be fitting for server, nor any other rolling distros. So I'd install something else on it. I could install NixOS, but installed openBSD instead, since it has honk, a fedi software I was thinking of hosting1. To my surprise, it was quite neat: typing feels much more responsive than any on other Linux distros I tried before, and startx works out of the box with the pre-installed FVWM (not a big fan of the WM, but it's quite easy to get used to). The OS is said to be friendly to old machines, after all.

Another thing I like about openBSD is the installation process. While not 100% non-tech friendly like Debian, installation is quite straightforwards with a series of questions. After installing NixOS on my laptop twice, I really appreciate not having to partition disks and configure boot manually.

New computer, new troubles

Yes, I bought myself a new computer. I'd rather have my laptop reserved for carrying around and my old machine as server only. The price for the set was acceptable, but I was a bit annoyed that they stole from me the joy of assembling it and tainted it with Windows cries.

Anyways, I digress. I planned to keep on NixOS: with a configuration file, replicating the settings and packages would be quite simple. Except when it doesn't. I don't know why, but the boot stucked at loading initrd. I guessed it might be some bug of systemd-boot (which NixOS manual states that I must use for UEFI, and legacy boot is not available as I don't have a dedicated graphic card).

I tried booting into its cousin GNU Guix. It can boot nicely without any trouble. What it couldn't is loading graphical interface properly: unlike NixOS, Guix doesn't provide an option to use startx for starting X, but rather push me to use gdm instead, which just shows a black screen for me. I tried several off-manual guides from mailing lists to forums---someone else surely had the same problems as me. None of them worked. Looking into the log, it says something along the line cannot load module fbdev... cannot load module vesa, even though I installed those xorg modules. Does that sound like a driver issue? I thought integrated GPUs are supposed to work out of the box.

Anyways, I forgot to dump or screenshot the logs in both case so no post-mortem for y'all.

I also tried Gentoo (why?) and that seems a bit too much. It's not as intimidating as some people might make it out to be, but it still takes to much time for me. I guess I'm just lazy now and install something straightforwards like OpenBSD.

The good

Aside from everything just works, here are some extras I like about it:

  • Accidentally closing tabs will trying to ^W some words is no more: Ctrl+W behaves the same way as in terminal. But then, so does Ctrl+A: it moves the cursor to the beginning instead of selecting all.
  • SSHD enabled by default. I don't use this much but it can be handy.
  • Packages have its own README to let users know something not detailed in the manual
  • Bug reporting is builtin (sendbug(1)), so I don't have to lookup some mailing list or creating yet another account in a bug tracker.
The bad

Admittedly, I find more annoying stuff than I liked:

  • UTF-8 is available, but not enabled by default. If some characters are not displayed properly in your terminal, try setting LC_CTYPE="en_US.UTF-8" in your .profile. I couldn't import GPG at first because of this and mistakenly thought it was because vi_VN.UTF-8 was missing.
  • Sound control is via sndioctl(1), which is supposed to be available to non-root users, but instead it default: can't open control device'd me. daemon forums says I'd have to add staff and operator, but it doesn't work either. Also, polybar doesn't support it, but it's not their fault.
  • Limited file access. This I can understand for security reason, but sometimes it's just annoying not being able to open some folders. KeepassXC integration also doesn't seem to work because of this, even though I followed the README and added to the unveil.
  • Limited file descryptors. This breaks syncthing and anything that watches files, like a development web server or static site generator in live-rendering mode.
  • Default shell is Korn (ksh), which doesn't have much completion, particularly git completion. Typing the whole commands out can be tiring.
  • Some packages are not available. In particular, nheko, the best native matrix client I've found so far, and dictd, the dictionary server. The former is just added and probably will be available in the next release, but the latter has been packaged for years and I have no idea why I can't install it.

Nonetheless, I am sticking with this for a while and gonna update some more.

(Despite the title, I'm still using GNU and Linux. Linux is on other machines, and bash and dico on this machines are also GNU.)


Footnotes
  1. Couldn't, though, since my ISP block HTTP(S) ports. There are some workarounds but I haven't got time to do yet.

https://xrvs.net/log2022-07-31-trying-openbsd
Introducing IPWHL: an alternative Python packaging
Show full content

This post was excerpted from discuss.python.org

What is IPWHL?

The interplanetary wheels are platform-unique, singly-versioned Python built distributions backed by IPFS. It aims to be a downstream wheel supplier in a similar fashion to GNU/Linux distributions, whilst take advantage of a content-addressing peer-to-peer network to provide a reproducible, easy-to-mirror source of packages.

On IPWHL, for each platform (architecture, operating system and Python implementation and version), there exists only one single built distribution. The collection of these distribution packages are given as a single IPFS CID. An installer can use solely this content ID and packages names to reproduce the exactly same environment on every platform.

The official IPWHL repository will provide exclusively free software. However, deriving the repository should be trivial and is a supported use case.

Why?

IPWHL is created as a curated and decentralized Python package repository.

PyPI repository is uncurated: anyone can publish a package there, which enables typosquatting and some other exploits. In contrast, by controlling which packages can go into IPWHL, we reduces risk of distributing malware significantly. Decentralizing the repository with IPFS makes mirroring more helpful and cost-saving. Additionally, by making the wheels singly-versioned, IPWHL is expected to save time for dependency resolution.

How to use IPWHL? Setting up IPFS

IPFS has a well-documented installation guide. It is worth noting that several GNU/Linux distributions and BSD-based OSes may have already included it in their repositories. Afterwards, please follow the IPFS quick-start guide. Some downstream go-ipfs packages may also contains a init-system service to automatically manage the IPFS daemon. By default, the daemon opens a local IPFS gateway at port 8080.

Use it

To use IPWHL repository, we can simply replace the PyPI URL to the repository through an IPFS gateway. For pip, you can do this by changing index-url:

pip config --site set global.index-url "http://localhost:8080/ipfs/$IPWHL_CID"

Mirroring a release is also as simple as pinning its CID:

ipfs pin add $IPWHL_CID
Feedback

IPWHL is in its early stage, so we would appreciate if you can let us know how you feel about it.

https://xrvs.net/log2022-06-19-announce-ipwhl
Comment calculer le calendrier lunaire et la découverte d'un site web ancien
Show full content
Comment calculer le calendrier lunaire

Je m'ai demandé il y a quelques jours comment on calcule le calendrier lunaire. Le nombre des jours de chaque mois, c'est facile : un sur deux mois est court et a 29 jours, l'autre est long et en a 30. Alors, 12 mois en ont 354 ; c'est 11 jours moins qu'un an. Donc, un sur trois ans, on ajoute un mois additionnel, de la même façon qu'on ajoute un jour additionnel tous les quatre ans (plus précisement, on divide l'années par 19, et si le reste est 0, 3, 6, 9, 11, 14, ou 17, on ajoute un mois).

Mais quel mois ajoute-t-on ? Dans un an solaire, on ajoute toujours le 29 au février, mais dans un an lunaire, au lieu d'avoir un treizième mois, on duplique un des mois (ex. en 2020, on a deux quatrième mois) il semble qu'il n'y ait aucun règle (il y a certainement un règle). Voici plurielles années et leurs mois extras :

année mois extra 1995 8 1998 5 2001 4 2004 2 2006 7 2009 5 2012 4 2014 9 2017 6 2020 4 2023 2 2025 6 2028 5 2031 3

Il y a deux réponses à cette question, comme tous les questions : celle du poète, et celle du savant. Le poète dit : quand les étoiles s'alignent. Le savant dit : quand les étoiles s'alignent ;)

Un site web ancien d'un compatriote

C'était tout l'information que je trouvais dans la première page du résultat de la recherche. Les résultats sont tous « c'est calculé en accordant avec la motion de la lune et la terre autour du soleil » mais jamais exacte comment.

Heureusement, je ne dois pas chercher trop. La réponse complète se trouve dans la deuxième page, dans un site web ancien. Il y a un guide en détails, mais avec des tas de nombres magiques dont personne ne sait la signification.

Je demandais qui avait tel une question comme moi, et je recognais son nom : c'est la même personne qui héberge un server DICT de dictionnaires vietnamiens, lequel Wiktionary vietnamien cite souvent.

Ça me intrigue. C'est très simple déducer son page accueil, qui semble mort : il y a des liens aux sites qui ne existent plus. Il y a quand même des choses intéressantes :

  • ses publications dans son uni, naturellement ; il recherche en informatique
  • des livres en domaine publique:
    • des records historique
    • un poème-roman classique
    • une traduction vietnamienne de Le petit prince
    • et cetera
  • il a étudié à beaucoup d'universités
  • il y a un tilde (~), qui signifie que l'uni lui a donné access de un server partagé par, j'assomme, des rechercheurs là, dans la même façon que j'ai un compte sur envs.net, un tilde plus moderne

C'est tout ‽ C'est vrai qu'il n'y a pas beaucoup de truc à dire de ça mais je le trouve intéressant que quelque chose que quelqu'un a écrit il y a 20 ans est encore sur internet, dont information peut aider quelqu'un. Peut-être le web n'est pas si éphémère comme je croyais. 20 ans dans l'avenir ce site web sera-t-il utile pour quelqu'un autre ?

https://xrvs.net/log2022-05-29-un-site-ancien
How to insert unicode in vim
Show full content

Today I learned how to insert unicode in Vim. It's simple:

In insert mode, type Ctrl+V, then type u, then type the hex unicode.

You can also type character like escape (shown as ^[) or carriage return (shown as ^M) by typing Esc or Enter respectively.

https://xrvs.net/notesinsert-unicode
Using tools with appropriate ability
Show full content

As you may have learnt from my previous posts (blog post and fedi posts), I'm writing a client for lotide named Luna. And you might also have seen me saying it's using Django as framework and my remark on it being unfitting. I am going to elaborate more on this in this post.

Django

Django is a batteries-included web backend framework. It includes:

  • web server
  • router
  • database connector and ORM
  • database migration tool
  • authentication
  • admin tool
  • command line framework
  • templating engines
  • internationalization tools
  • form handlers
  • email

There is a lot more that can be installed as plugins, and some of these tools can be unplugged from the INSTALLED_APPS in the settings. However, given that the default contains so much, it can be troublesome to get a minimal setup.

Luna

On the other hand, Luna is just a frontend for lotide (a server-side-generated frontend, not to be confused with JavaScript front end), alternative to its official front-end hitide.

I started writing this client (with Django) because:

  • This project (lotide) seems interesting and I'd like to work with it.
  • I don't like the default client. Not that the default one is bad.
  • Unfortunately, I don't program in Rust, and am not willing to, so contributing directly to hitide is unlikely.
  • This web framework (Django) seems popular, and some experience with if is required for some jobs so /shrug

It doesn't need database handling, or administration, or emailing. I would definitely have no use for most, if not all of djangoadmin and manage.py commands.

Django is an overkill for Luna. Using a big tool for small task feels very clumsy, with all the tools you won't use and tools that you have to use.

Alternatives

As I get quickly annoyed with the generated manage.py and the lengthy settings.py getting out of hand, I haven't worked on the client for quite a while, and am planning to drop development if no one else is taking over.

I have also tried to rewrite it in Go, but it seems internationalization support for Go is quite lacking. So, I backed to a more familiar stack using Quart (with better support for asynchronous programming, though it might be another overkill) and Jinja (which has somewhat matured internationalization support) and renamed it to a less common name (Yue) to avoid name conflict which led me to call Luna's package lotide_luna. I'm less motivated than before, and also have less time, so this will move much more slowly.

https://xrvs.net/log2022-01-31-luna-django
Implementing DICT protocol: Part 1
Show full content
DICT Protocol

What is DICT protocol?

The Dictionary Server Protocol (DICT) is a TCP transaction based query/response protocol that allows a client to access dictionary definitions from a set of natural language dictionary databases.

DICT Protocol - RFC 2229

Notable implementations for this include dictd and GNU dico(d); the former is the reference implementation that supports multiple database formats, as listed in dictfmt (1).

I intend to implement a server and multiple clients (CLI, GUI, web) to this protocol, as well as some tools to easily create a dictd-readable database.

Why?

No practical reason, but dict is one of the first command line tool introduced to me and easily one of my favorite, along with curl and jq. It's basically just a dictionary app, but it's cool:

  • works perfectly in terminal
  • easily self-hostable
  • fast
  • has cool dictionaries (though only Debian, Arch and derivatives distribute those)

Also, I'm writing dictionaries for my conlangs and I want to distribute them via this protocol. Clearly, implementing a server that is already implemented doesn't help, but I tend to go down rabbit holes.

I also like to explore non-web protocols, and starting with something simple like DICT might be a good idea.

Reading the spec

The spec (linked at the top of this post) is shorter and easier to read than I thought. Ignoring the introduction, examples and citation, it's les than 20 pages. There are five classes of commands:

  • Querying the database: DEFINE, MATCH
  • SHOW metadata about the servers and the databases
  • Utilities: informing CLIENT name, check STATUS, show HELP, show OPTION and QUIT
  • Authentication: AUTH and SASLAUTH

The authentication ones are optional, and I don't find that useful, so I won't implement it anyway, this limits to the first three categories.

Handling TCP

DICT is based on TCP, and there is a neat interactive TCP tool called telnet, which I used for testing the commands.

telnet

DICT runs on port 2628:

$ telnet dict.org 2628
Trying 199.48.130.6...
Connected to dict.org.
Escape character is '^]'.
220 dict.dict.org dictd 1.12.1/rf on Linux 4.19.0-10-amd64 <auth.mime> <89168346.27665.1642303045@dict.dict.org>

Let's try out some commands to understand how this work. Note that I prefix the command with ~> here so that it stands out of the response, and truncate long results with [...].

Let's first show what databases there are

~> SHOW DB
110 166 databases present
[...]
.
250 ok

There are a lot of dictionaries here, including GCIDE, WordNet, The Jargon File, V.E.R.A., FOLDOC, but most of them are FreeDict dictionaries.

To a word, the syntax is

~> MATCH database strategy word

Strategy is how the server will match the word you're looking up. To list all strategies available, send the command:

~> SHOW STRATEGIES

There are various strategies supported by dictd, for example, substring, which matches if the entry has the queried word as substring:

~> MATCH jargon substring program
152 13 matches found
jargon "c programmer's disease"
jargon "cargo cult programming"
jargon "mickey mouse program"
jargon "perfect programmer syndrome"
jargon "program"
[...]
.
250 ok [d/m/c = 0/13/5775; 0.000r 0.000u 0.000s]

This command only show which words in the database, if any, satisfy the match, without showing the definition. To actually view a definition, one has to supply the dictionary name to the DEFINE command. Note that, you can also use * for both DEFINE and MATCH command, which will define/match for all dictionaries.

~> DEFINE * programming
150 3 definitions retrieved
151 "programming" wn "WordNet (r) 3.0 (2006)"
programming
    [...]
.
151 "programming" jargon "The Jargon File (version 4.4.7, 29 Dec 2003)"
programming
 n.

    [...]

.
151 "programming" foldoc "The Free On-line Dictionary of Computing (30 December 2018)"
programming

.
250 ok [d/m/c = 3/0/145; 0.000r 0.000u 0.000s]

That's a gist of how to look up words with DICT protocol. You can find more commands with:

~> HELP
[...]
.
250 ok

Finally, to end the session, the command is:

~> QUIT
221 bye [d/m/c = 0/0/0; 123.000r 0.000u 0.000s]

Note that, the response always ends with a period and a 250 ok response---this is equivalent to HTTP's 200 OK---except for QUIT. These response code are defined in the protocol specification.

Commands other than HELP has some additional statistics, though this is optional. I figured out that d means definitions, m means matches, and s is probably the time it took to query (why are they always zero, though?), but no clues on what c, r, and u mean. I might check the source code to figure that out, but let's leave it for another time.

Go

Of course we are not going to make the users type these commands (though it's not too unintuitive and can be easily remembered). I chose Go to build the CLI client, though without any conscious consideration of fitness. I'm trying out new things1 after all.

From the doc, we can figure out how to make a TCP connection.

conn, err := net.Dial("tcp", "golang.org:80")
if err != nil {
	// handle error
}
fmt.Fprintf(conn, "GET / HTTP/1.0\r\n\r\n")
status, err := bufio.NewReader(conn).ReadString('\n')
// ...

Let's copy that and replace with DICT command instead of HTTP:

conn, err := net.Dial("tcp", "dict.org:2628")
if err != nil {
	panic(err)
}
defer conn.Close()
buf := bufio.NewReader(conn)
fmt.Fprintf(conn, "MATCH jargon word programming\n")
fmt.Fprintf(conn, "QUIT\n")

for {
	response, err := buf.ReadString('\n')
	if err != nil {
		// oftentimes this is EOF error
		fmt.Println(err)
		break
	}
	fmt.Printf(response)
}

Running this code, we get response:

220 dict.dict.org dictd 1.12.1/rf on Linux 4.19.0-10-amd64 <auth.mime> <89266600.1914.1642341395@dict.dict.org>
152 4 matches found
jargon "cargo cult programming"
jargon "programming"
jargon "programming fluid"
jargon "voodoo programming"
.
250 ok [d/m/c = 0/4/3814; 0.000r 0.000u 0.000s]
221 bye [d/m/c = 0/0/0; 0.000r 0.000u 0.000s]
EOF

which is a good start.

There is a problem with this code: currently we are reading line by line, rather than reading the whole response for each command. We can't know if line 3 is response for the first command or the second this way. A solution is to check if the line is prefixed with a status code, but do we have a better solution?

Let's wait till next week!


Footnotes
  1. Not really, I've written a CLI client for Wiktionary API with Go before.

https://xrvs.net/log2022-01-16-dict-1
Les bonnes résolutions d'année 2022
Show full content

Bonne année ! Je vous souhaite de la bonne santé, comme la santé c'est la plus importante pendant cette pandémie.

J'écris ces résolutions en français parce qu'il y a longtemps que je n'ai pas écrit en français. C'est la premiere résolution : ne pas perdre mon français. C'est pénible que ma capabilité se réduit comme je le négliger. En le faire, je vais écrire plus en français et finir lire le roman que j'ai commencé l'année dernière.

Je me promise de ne plus rester éveillé tard. Me coucher tard a eu un impact grave à ma santé. Comme je m'ai couché tard, c'était impossible de me réveiller tôt. Comme résultat, je n'ai pas eu le temps pour des exercices. Alors, j'ai pris du poids et me sens mal.

Pour contribuer plus aux logiciels libres, je vais herberger des services, particulièrement ceux fédérés (fédivers). Comme un développeur web, je le trouve frustrant que je ne pouvais pas partagé mes logiciels avec mes amis. Je vais aussi faire des dons aux développeurs et herbergeurs des logiciels qui m'est utiles.

J'espère que je peux prendre plus de temps pour mes hobbies. Je souhaite que ma première conlangue est finie et je peux y traducter mes pubs et logiciels. Je voudrais que je puisse dessiner et écrire plus aussi.

Je voudrais gagner plus au travail, mais ça c'est dépendant de plusieurs facteurs externes, alors je ne vais rien espérer.

https://xrvs.net/log2022-01-01-new-year
2021: End of year sum up
Show full content

So... it's the end of the year, let's look back at what I've achieved this year.

Starting a blog

I felt like it was longer, but it turns out I started writing a blog right at the beginning of this year. I tried switching around multiple solutions, namely Wordpress, WriteFreely, to static site (Jekyll) hosted on GitHub, later SourceHut and currently on a tilde, envs.net. I'm quite content with the current clean setup and not planning to change.

Graduating & getting a job

It's also the end of my life as a university student. I intended to go for higher study, but due to my lack of planning, I missed my opportunity. On the other side, I believe me getting my current job is another chance to acquire more experience in programming and teamwork as well as some cash to fuel my hobby. While this means I spend less time developing free software, I feel better having an income and not staying in an idle state.

Leaving Facebook

While escaping from Big Tech is impossible, I have made a step away from it. Facebook is a bad actor of the web, and I do feel less irritated after I left it. On the contrary, the current social media that I use, Mastodon and Pleroma, are full of nice and chill people---not that there aren't heated ones or trolls, but it does feel like the lacking of a manipulative algorithm does make the content better.

Of course I'm biased, so try it for yourself.

Being more active in free software contribution

I don't lead a big free software and I also am not a regular contributor for any particular project, but I did participate in them.

Discovering the fediverse

While I joined Mastodon since the end of last year, it is not until this year that I tested out other various ActivityPub-based federated projects: PeerTube, Pleroma, Misskey, PixelFed, Lemmy, lotide, WriteFreely, and Bookwyrm. They are all great projects.

You might question the fediverse's premise: what is the point of making it possible to interact with another server which uses entirely different software? I just think it's great that we can freely choose whichever software that suits their need and still can communicate with each other, whether it's the nice and simplistic Mastodon, the customizable Pleroma with richer features, or the even more feature-rich Misskey.

I also tried out federated chat protocols, Matrix and XMPP, and I'm sticking with Matrix because its clients look nicer generally, and they often handle E2EE in less headaching way. I also try out IRC during the Freenode drama, and decided to stick to a lesser known server, hackint.org. The channel for IPWHL is thereon.

Packaging

If you subscribe to my blog or follow me on social media, you probably have known about Floating cheeses, more formally known as IPWHL, which is a Python distribution on IPFS and has its dependency tree pre-resolved.

I also try packaging packages for GNU/Linux distributions I use. I tried to add Badwolf, a minimalist web browser, and update the outdated noto emoji font on openSUSE, though the submissions sadly were not approved. Though, the update nheko on Void Linux1 to version 0.9.1 was.

Contributions to other projects

I have contributed a fairly large amount of code to projects I use. There are tiny ones, such as correcting typos or updating outdated information, and there are non-feature ones, such as adding IPA layout to Florisboard.

I also fixed a bug on flit that prevents people with non-ASCII characters in their names to have them written correctly in the pyproject.toml file, and added another redirect to Privacy Redirect add-on, though sadly the latter still hasn't been fixed.

Writing my own pieces of software

Using badwolf, I found it cumbersome to write a small tool to generates XBEL from a YAML template. Unsatisfied with the default lotide frontend, I decided to write an alternative one, Luna2, and later, Yue. I've given up on my attempt to rewrite a conlanging tool PolyGlot, since I realize using such software is against my plain text workflow. It's true that a software is only born when it scratches the developer's itch.

Advent of code

I've participated in the first half of Advent of Code for the first time, but it is increasingly less fun to do, so I dropped it prematurely.

Rekindle my old hobbies

I have hobbies of making creative works, like many other nerds on the internet: drawing, conlanging, world building, writing fiction. Though I only keep them to myself, it feels great to come back after a long time neglecting them. I have a few scribbles on here, though they're not representative of my drawing (they're representative of my drawing with mouse, on a computer), not that I draw that well.

Next year

Looking back at these achievements in the last year is inspiring me to plan for the next, though I'm leaving the details for tomorrow.

  • Host an public IPFS node to support IPWHL
  • Make more advancement in developing Yue
  • Make a Mastodon bot for the sake of it?
  • Try out the gemini protocol
  • Renewing my IELTS certificate and take a DELF test
  • Publish my first usable conlang and start translating my blogs and software to it?
  • Start drawing a comic with ideas I'm having in mind?
  • Donate to good software and services I use

I guess I should have more coherent plan but as I said, let's leave it for the new year.


Footnotes
  1. Sorry to the majority of poll voters, I don't make decision based on polls

  2. Sorry again, I promised this one will be about Luna but well, let's save it for the lunar new year.

https://xrvs.net/log2021-12-31-year-end
Cars vs Bikes: The space efficiency
Show full content

A few days ago, I said somewhere on the internet saying cars are the cause for traffic jam and motorbikes are much faster in a city. Today I'm gonna to do one of the most useless things: doing the actual calculation to verify this obvious intuition. Carry on if you're bored enough like I am right now. It's a lot of guesstimate, though, since cars as well as bikes come in all shapes and sizes.

I take some numbers from automobile dimension and Power Sports Guide: a car is around 4.4 meters long and 2 meters wide; a motorbike is around 2 meter long and 0.75 meter wide (less than 200 cc, the common type driven in the city). An average car is therefore 8.8 square meters large and a motorbike 1.5.

A car can take around 5 to 8 people, while a motorbike can only takes 2 people (by law---I'm sure some people can carry twice that number). So, the maximum capacity for a car is roughly 0.6 to 0.9 persons/m², and that for a motorbike is 1.3 persons/m².

Hmm... that doesn't seem like much difference, but a motorbike is twice as space-efficient as a car. Imagine you suddenly have twice as much space for driving.

Well, that's the ideal scenario, which is not usually the case, though. Most the time, the one doesn't fill up all the available room on a car. In daily travel, a car carries up to 4 people: parents go to work, children to school, and I'm fairly certain that their workplaces and the schools are often far enough they drive a significant distance with much less passengers. Oftentimes, I only see 1 person/car, but let's be generous and say there are 2 people on average. That'd be 3 times less space-efficient than an average motorbike with only 1 person (0.24 vs 0.67 persons/m²).

Conclusion: Motorbikes are better for the city. Cars can awesome for occasion when you want to go to a different city or when the weather hates you. But in terms of daily travel, motorbikes rock!

Addendum: a city bus is much more efficient than both of them: having a capacity of around 100 people on a 30m² of area, which is 3.3 persons/m², it is 5 times better than a motorbike. In contrast with cars and motorbikes, buses are often full during rush hours when traffic jam most likely happens. While buses suck more when it's stuck in a traffic jam due to its size (just like cars), at least you can enjoy a good book on it. Public transport rocks!1


P.S. Sorry for the boring update after a long while not writing. Kinda caught up with life including frustratingly moving around in the city. I'm writing a web client for lotide, named Luna, using the least fitting web framework ever, by the way, and I'm adding matrix implementations to my long lists of cool things I could try to work on if I don't need money to live. Next post will be about Luna, the lotide client and how I'm using the least fitting library for it.


Footnotes
  1. Except when you wait for it for hours at the stop due to, well, traffic jam, or when you hurt your hands when the bus brakes, because heavy things have big momentum.

https://xrvs.net/log2021-11-16-cars-vs-bikes
How to hide decorative anchor
Show full content

As you might have noticed, I recently have some changes to the website design. The changes are mostly for accessibility and readability: the text is now bigger so it's easier to read and select. I also color visited links, so people can spend less time knowing they've read a link (haha who wants people to spend less time on their site I must be crazy right?). One of the changes it the heading anchor with a decorative "#" to make visual readers recognize the heading levels easier. However, if this would probably be announced as "hash" by screen readers, which wouldn't make sense. In this post, I'd show how I ended up with the current one.

Simple heading anchor

Heading anchor can simply be done by wrapping the content inside the anchor element, <a>, which refers to the ID of the heading:

<h2 id="foo">
  <a href="#foo">Foo</a>
</h2>

This is very simple, and it works perfectly fine. However, I also would like to have a fancy visual cue in front of the heading some website use a chain link, but I prefer to use a simple character instead of an image. The hash symbol makes sense for me, since I mostly use Markdown. I use heading level minus 1 hashes for this (since heading level one is not used within a post). Using ::before pseudo-element, this is rather simple:

h2::before {
  content: '# ';
}
h3::before {
  content: '## ';
}
...

Unfortunately, this leads to an accessibility problem as stated at the beginning of this post: screen readers (inconsistently) announces this as "hash". Multiply that with the level of heading and imagine the nuisance.

There are several proposals to solve this, which is discussed in following sections.

Alternative text for pseudo-element

I've read from some StackOverflow answers1 that you can add alternative text for pseudo-element by doing this:

h2::before {
  content: '# ';
  content: '# ' / '';
}

I don't know which browser supports this; it looks like bad syntax. The answer itself said this is non-standard. And I'd tell you this is not supported by Firefox, at least.

Using aria-hidden content and visually hidden description

So, I found a blog post whose content is similar to this one. It is likewise a long post, but in short the method is instead of using a pseudo-element, you can use aria-hidden decorative element with a visually hidden text. It looks like this:

<h2 id="foo">
  <a href="#foo">
    <span aria-hidden="true">#</span>
    <span class="visually-hidden">Section titled Foo</span>
  </a>
  Foo
</h2>

This method is also used by HTMHell and MDN's social icons (though, HTMHell call the class u-hidden and use sr-only in their instruction).

Here is how MDN styles the visually hidden class:

.visually-hidden {
  border: 0;
  clip: rect(0 0 0 0);
  height: 1px;
  margin: -1px;
  overflow: hidden;
  padding: 0;
  position: absolute !important;
  width: 1px;
}

This, however, looks bad if you read my posts from a RSS reader, as a RSS reader may not support all HTML tag and doesn't have the CSS for the visually hidden element at all. The heading appears as "# Section titled Foo Foo", which is rather hideous, if you ask me.

Current solution

It is a rather simple combination of the previous approach and my original approach: use aria-hidden decorative icon, and use ::before pseudo-element to avoid it rendering in the RSS feed.

HTML:

<h2 id="foo">
  <span class="decorative" aria-hidden="true"></span>
  <a href="#foo">Foo</a>
</h2>

CSS:

h2 .decorative::before {
  content: '# ';
}

I have only tested this on Firefox with orca as screen reader, though I expect it to do well on others as well.


Footnotes
  1. one of a rather less reliable source of knowledge, yet commonly used by many people

https://xrvs.net/log2021-10-16-how-to-hide-decorative-anchor
How NOT to mess with the scroll bar
Show full content

Be prepared to use your scrollbar a lot, since it's topical 😉1.

GTK custom styling

Recently, I've tried BadWolf, a browser written by someone I found on the Fedi. It's quite amazing if you are interested minimalism---the memory footprint is very small, even smaller than qutebrowser, a quite well-known minimal web browser. I've packaged it on openSUSE thanks to OBS. That being said, its feature is quite limited---bookmarks and history, for examples, are not quite well-supported.

But I digressed. I brought up BadWolf not because it is great (should I rather say, small), but because by using it, I now know how to configure GTK window styling with CSS. On a Unix-like system, this CSS file would be located at $HOME/.config/gtk-3.0/gtk.css. I configure my scrollbar like this:

scrollbar, scrollbar slider {
    min-width: 10px;
    min-height: 20px;
}

scrollbar {
    background: #111;
}

scrollbar slider {
    background: lightblue;
}

scrollbar slider:hover {
    background: lightgreen;
}

The default GTK scrollbar is way too small, so I enlarge it to at least 10 pixels to make it more clickable. The default scrollbar is like grey on... darker grey? That's not very contrast, so I make it light blue on dark grey background to be more visible. Additionally, it turns green to let me know my mouse is at the right place when I need to scroll. Additionally, I also set GTK_OVERLAY_SCROLLING=0 so that it doesn't disappear when I don't hover on it. (Yes, I have a very peculiar expectation.)

BadWolf's compliant scrollbar with expected
width

The troublemakers

And then here comes the developers who decided to mess with the scrollbar: it now looks ugly at best and unusable at worst. GTK unfortunately doesn't have !important in its CSS to disallow these overriding.

Firefox

So when I go back to Firefox, I'm slightly disapointed to see that it does not follow my CSS: the scrollbar is smaller than 10 pixels:

Firefox's scrollbar with smaller width

Well, at least it's still clickable somewhat, I guess. I've seen smaller bars.

LibreOffice

On the opposite side, LibreOffice's scrollbar is just way too large:

LibreOffice's scrollbar with extravagant
width

I haven't looked at the source for LibreOffice, but it seems they even increase the scrollbar size to be way too large. This doesn't make it less usable by any mean, but it's an eyesore for me.

Mastodon

Who knows that it's not only the apps, but also the websites who are not supposed to modify our software, can style the scrollbar to our dismay? As much as I like Fediverse, this scrollbar is the thing I like the least:

Mastodon's scrollbar without round border

Just by looking at this, do you know which part is the slider (the thing you hold on to scroll)? That's why we have margin and border. Please don't change this!

But well, what's worse is that...

Mastodon's scrollbar without contrast color

I just hover the slider and it's GONE.

Note that this is done on BadWolf. On Firefox, thanks to them messing with the scrollbar, the problem just stops at the contrast.

Stylus to the rescue

Earlier, I have given a shoutout to Stylus. I'd like to give a shoutout again: Thanks Stylus for protecting me from these websites who like to mess with my scrollbar! I have a custom style named "No one dares override my GTK config" which is like this:

html {
    scrollbar-color: inherit !important;
    scrollbar-width: inherit !important;
}

Of course, this can't change Firefox's or LibreOffice's modification, but now I'm at peace knowing that websites can no longer tamper with my scrollbar. 🙂


Footnotes
  1. I know you can use the mouse scroller, but if you're like me and have a mouse that sucks, using the scroller is quite painful by itself. On related note, I've finalized a JavaScript script that helps generating index from heading, thanks to a discussion on Fosstodon. You may find scrolling with headings easier using this script.

https://xrvs.net/log2021-10-08-sane-scrollbar
[Web Horror] Background Image
Show full content

CSS allows you to set background for an element with an image with background-image attribute. However, as recommended by Mozilla, you should always set a background-color, because:

If the images cannot be loaded—for instance, when the network is down—the background color will be used as a fallback.

You can encounter some of websites that don't follow this:

The cookie popup dialog on tchncs.de has an image of (literal) cookies as background, and this is what it looks like when the images can't load:

An unreadable popup dialog on tchncs.de due to bad background
contrast

FSFE makes the same mistake on their website for Public money? Public Code!

An invisible open letter due to no contrast

I was with a terrible internet at the time I visited it, and I didn't realize the text was loaded until I double clicked on it.

Thanks to caching, this problem wouldn't be too significant if you revisit the website often, but setting a fallback color is still a recommended practice.

I would even say one should avoid direct background on text if possible. Even when the image is fully loaded, the text can be hard to read on similar colored background.

Blue link that is hard to read on bluish background

Images that are without simple pattern should be avoided at all cost: it is practically impossible to choose a color to contrast all of them. The image below, taken from publiccode.eu, is an example.

Text on images with too many details is hard to read


This is not to shit on the mentioned websites. FSFE fights for software freedom and tchncs.de hosts various services for free, which is why I care. These websites however serve as bad examples in term of readability with such backgrounds.

https://xrvs.net/log2021-09-21-background-image
Facebook censors links to Mastodon
Show full content

On Facebook, toxic comments that dehumanize and wishing death on a large group of people are "not violating our Community Standards". Posts that introduce to friends some decent software, are.

I am not alone

As I started writing this post, I found out that someone else named John Goerzen had the same experience as me: post removals due to a link to Mastodon's page, with no option to appeal! From the comment of the post, I found another fedizen with username yhancik experiencing the same problem. On John's post on his Mastodon account, there is a reply claiming the same problem happened when he mentions Diaspora, and another reply mentioning his shadowban from Twitter (ha, Facebook is not alone either).

Why?

They claim this (a post inviting people to join the fediverse, with detailed explanation) is spam. They claim that this is to prevent things like false advertising, fraud (they allowed advertisements of counterfeit cash and false ads of mobile games, the latter of which is an infamous phenomenon), and security breaches (need I to say, Facebook is the security breaches).

Irony1 asides, let's give them the benefit of the doubt and assume they actually used some automatic tools to do it to curb spams, then there is no reason not to allow appellation. As a data mining company, Facebook should know better than anyone else that machine decision based on data, so-called "artificial intelligence" (a more appropriate term is "artificial stupidity") is never reliable. The decision to ban mention of Mastodon therefore seems deliberate. Hence my rather proofless claim in my Mastodon post.

Some people pointed out how ridiculuous that claim is: Mastodon, and even the whole Fediverse, is insignificant compared to Facebook. I agree with that. However, the hypothesis is totally convincing if you consider that they wouldn't want to let users know what a free2 social media is like.

Respect yourself, say "no" to disservices

Censorship is far from the only injustice Facebook et al have done. Dr. Stallman has compiled a list of reasons to to be used by Facebook. I was trapped there with the reasons of keeping contact with my friends, though this is more of a FOMO: I have their emails and telephone numbers to contact with, even if they're not willing to move to a more ethical and secure platform.

The reason I deleted Windows from my machine and switched to Linux was the same: I would be disrespecting myself if I keep using those disservices3. For the same reason, quitting Facebook is the only sensible choice. Would you make the same decision?


Footnotes
  1. I would like to thank my anonymous friend who pointed out to me the irony that I should've realized earlier

  2. Free in both freedom and free of charge. Facebook is as a free service to its useds as dairy manufacturers to cows.

  3. Before some pedant pointing out Windows is not a service, I would say that using something without owning it is some kind of hiring service.

https://xrvs.net/log2021-09-12-facebook-censoring-mastodon
IPWHL: August update
Show full content

On Monday this week, we have released our weekly release of the cheeses.

With the increasing number of packages declared in our repository, it is harder and harder to keep track and decide which packages to package next. To address this, McSinyx has written a script to check which packages can be declared next:

from importlib.metadata import distributions
from glob import glob

from packaging.utils import canonicalize_name

def declared(project):
    name = canonicalize_name(project).replace('-', '_')
    return glob(f'ipwhl-data/pkgs/*/*/{name}-*')

for distribution in distributions():
    name = distribution.metadata['Name']
    if declared(name): continue
    for dep in distribution.metadata.get_all('Requires-Dist') or []:
        if not declared(dep.split(maxsplit=2)[0]): break
    else:
        print(name)

On the other hand, I have written a tracker page to see how far we have progressed this project. Packages that have not been declared yet are emphasized and colored red1.


Footnotes
  1. I'm not sure if this is accessible enough for colorblind readers and readers who use text browser or screen reader. If you're aware of a better way to highlight please tell me.

https://xrvs.net/log2021-08-26-ipwhl-update
Recommendation: SourceHut, a decentralized hosting service
Show full content

So, a few days ago I've had a conversation from my Pleroma account concerning SourceHut and git hosting in general. Original thread here.

So in short, OP wanted to move out of GitHub, for some reasons, probably because it is:

  • centralized
  • non-free1
  • hosted by Microsoft
  • have been hostile to developers
  • exploit free software2

How ironic, because free software developers use a non-free service for development, which is a centralized service that wraps around a decentralized protocol.

Decentralized hosting

Let's ignore that thread for a moment. If you want to avoid GitHub (and you should), there are two solutions:

  • Use some other hosted instances, like codeberg.org or notabug.org
  • Self-host your own instance, using free software like gitlab, gitea, or even a barebone git server like cgit

But this decentralization introduces a (not) new problem: how to contribute to another project, when the project is hosted on a different instance than yours? There are three options:

  • Create yet another account to contribute
  • Meh I can leave them be
  • Make a pull request

But how do you make a pull request without having an account there? If you clicked on the link above, you can see how:

  1. host their clone somewhere publicly available---could be anywhere
  2. push up a branch with the change
  3. contact you via email or slack or whatever to "request" that you "pull" the changes in that branch/remote
  4. feedback/discussion continues out of band, contributor can push updates to the branch, you can pull them down to review
Illustration of how it works

In fact, this is described as one of the common workflows for git and is what happens behind the scene in GitHub pull request (except a GitHub PR pull directly to maintainer's remote and the remotes must be hosted by them).

Mailing list workflow

It is reasonable to assume everyone has an email. After all, most services require emails for registration, and more importantly, to git commit you have to set an email value. On the contrary it's not reasonable to assume someone have a GitHub account. Therefore, it makes perfect sense to accept contribution via email, regardless of your hosting service or main contribution model.

Without an explicit instruction, the contributor can find the maintainer's email address via git commits. However, this raises a problem: the communication is private. Thus, a public mailing list is important for an open atmosphere.

SourceHut makes this easier: it provides mailing lists that you can use for issue tracking, discussion, and contribution. It is free software, and works without JavaScript, which provides a clean and lightweight UI, though it might not be as eye-catching as some people expected, but that's subjective.

I am aware that one of the project maintainers had (have?) been rude towards others inside the free software community, and therefore many people wouldn't want to support him. However, the project is free software and licensed under an AGPL license, which means you can:

  • self host your own instance
  • maintain your own fork so you don't have to depend on them

And if you really don't want to use anything associated with him, I'd still hope you would host a mailing list for your free software projects. Let's keep the free software development stay away from proprietary platforms and keep it decentralized!


Disclaimer: I'm not affiliated with codeberg.org, notabug.org, or sr.ht


Footnotes
  1. I know services can't be free or non-free, but I still use it in the sense that the application that runs the service isn't free.

  2. The most recent example is Copilot, which mass-exploited free software for training a code-generating model.

https://xrvs.net/log2021-07-03-decentral-contribution
IPWHL: Maybe the real cheeses are the packages we helped along the way
Show full content

So, according to a recent announcement in the mailing list, I now co-maintain the IPWHL project, also known as Floating Cheeses (I prefer the latter for it being more playful and pronounceable, but IPWHL is just quicker to type). So, I feel obliged to provide a more thorough introduction.

Les Cheeses

In short, IPWHL is a PyPI alternative (though, initially the package database would be collected from there). What it provides:

  • Decentralization
  • Security
  • Reproducibility
Decentralization

IPWHL uses IPFS for storing packages. This provides several advantages:

  • No single point of failure
  • Easy to mirror
  • Faster download thanks to P2P

As there have been several incidents of PyPI outages, this is a strong reason to use our cheeses.

Security

No single point of failure is a security feature itself, but besides that, IPWHL also is more secure because:

  • We have CIDs and we cryptographically sign the packages thanks to merkle dag
  • We avoid packaging packages that are typosquat attacks
Reproducibility

IPWHL has a pre-resolved dependency and its packaging strategy is similar to NixOS, a distro known for its reproducibility. The installer can uses the CID and the package name to reproduce exactly the package.

Current problems

Despite the theoretical advantages, IPWHL is a new project and thus has several problems

Lack of packages

A distribution doesn't mean anything without packages, and IPWHL is indeed in need of them. As of the time I am writing this (2021-06-21), there are less than 100 packages declared in the database.

Introducing more packages would lead to maintenance problem: we cannot, as two sole maintainers, keep up with too many packages and make sure they're all up-to-date. Therefore, please, come help us if you're interested in this project.

Dependency Hell

Maybe you've heard of npm install is-even meme, if you hang out in some programming meme groups. It represents an underlying problem of having too many packages depending on each other. PyPI is saner, I would say, but it does have that problem.

xkcd comics "Dependency":
  A tower of blocks is shown. The upper half consists of many tiny blocks
  balanced on top of one another to form smaller towers, labeled:
  "All modern digital infrastructure"
  The blocks rest on larger blocks lower down in the image, finally on a
  single large block. This is balanced on top of a set of blocks on the left,
  and on the right, a single tiny block placed on its side. This one is
  labeled: A project some random person in Nebraska has been thanklessly
  maintaining since 2003
Original XKCD comics shared under a CC-BY-NC 2.5 License.
Transcript retrieved (with some edits) from ExplainXKCD shared under a CC-BY-SA 3.0 License.

I would even say if the package dependency were like the above illustration, it would be simple. In reality, circular dependency makes it impossible to declare one package without declaring the other, which can be demonstrated by this tensegrity shape:

a tensegrity structure
A tensegrity structure, drawn by me

Or, in some cases, such as for tox, it can even be like this:

A tensegrity icosahedron made from straws and string
Icosahedral tensegrity structure, retrieved from WikiMedia, authored by QuarterNotes, shared under a CC-BY-SA 4.0 License.
How to help Rick from *Rick and Morty* dancing and singing 'I'm begging for
help'

Due to mentioned problems, it is critical for the project to have contribution. To start, please take a look at the manual page

Help declaring packages information

Currently, the packages with high priority is listed here:

https://todo.sr.ht/~cnx/ipwhl/5

Write docs

User and contribution manuals are not really clearly written, and some information are scattered across the mailing lists. You can help by compile them into a comprehensive structured manual.

Help resolving dependency conflicts

Maybe the real cheeses are the packages we helped along the way

Some packages cannot be in its latest version, due to some constraints.

A case in point is Sphinx, which depends on docutils and docutils-stubs. The latest version for docutils is 0.17.1, but so far the latest version for docutils-stubs depends on specifically 0.14 versions. Due to this conflicts, you can only install docutils 0.14.

Therefore, you can help developing and packaging docutils-stubs so we can have newer packages on IPWHL

https://xrvs.net/log2021-06-21-ipwhl-update
Recommendation: jq is a powerful JSON processor
Show full content

So lately I've been using jq quite a bit. It is a CLI JSON processor that makes your life easier if you have to deal with a lot of JSON. Here I'm going to give two examples of how it's used.

Search response

I've been dealing with JSON from Elasticsearch API1, but as they would not release their documents under a free license, I will take an example from OpenSearch instead2:

{
  "took": 39,
  "timed_out": false,
  "_shards": {
    "total": 68,
    "successful": 68,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 5837,
      "relation": "eq"
    },
    "max_score": 7.8623476,
    "hits": [
      {
        "_index": "new_shakespeare",
        "_type": "_doc",
        "_id": "100763",
        "_score": 7.8623476,
        "_source": {
          "type": "line",
          "line_id": 100764,
          "play_name": "Troilus and Cressida",
          "speech_number": 43,
          "line_number": "3.1.68",
          "speaker": "PANDARUS",
          "text_entry": "Sweet queen, sweet queen! thats a sweet queen, i faith."
        }
      },
      {
        "_index": "shakespeare",
        "_type": "_doc",
        "_id": "28559",
        "_score": 5.8923807,
        "_source": {
          "type": "line",
          "line_id": 28560,
          "play_name": "Cymbeline",
          "speech_number": 20,
          "line_number": "1.1.81",
          "speaker": "QUEEN",
          "text_entry": "No, be assured you shall not find me, daughter,"
        }
      }
    ]
  }
}

Woah that's long!

How do you know how much time it took? I want to measure the performance.

$ curl ... | jq '.took'
39

Nice. Not very helpful, though---I can easily see that at the top of the result. I want to see which responses were returned.

$ curl ... | jq '.hits.hits'
[
  {
    "_index": "new_shakespeare",
    "_type": "_doc",
    "_id": "100763",
    "_score": 7.8623476,
    "_source": {
      "type": "line",
      "line_id": 100764,
      "play_name": "Troilus and Cressida",
      "speech_number": 43,
      "line_number": "3.1.68",
      "speaker": "PANDARUS",
      "text_entry": "Sweet queen, sweet queen! thats a sweet queen, i faith."
    }
  },
  {
    "_index": "shakespeare",
    "_type": "_doc",
    "_id": "28559",
    "_score": 5.8923807,
    "_source": {
      "type": "line",
      "line_id": 28560,
      "play_name": "Cymbeline",
      "speech_number": 20,
      "line_number": "1.1.81",
      "speaker": "QUEEN",
      "text_entry": "No, be assured you shall not find me, daughter,"
    }
  }
]

The response is the value of hits inside a hits, so the query is .hits.hits. How intuitive!

But this is hard to read. Let's just take the play_name of the result.

$ curl ... | jq .hits.hits[]._source.play_name
"Troilus and Cressida"
"Cymbeline"

Neat! But how does it work? The brackets [] signifies that we should take all the results, from each of which the value for ._source.play_name is taken.

There might be the option to write that in the search query DSL, which also reduces the data transferred, but I bet this is much easier to write.

Wallpaper

I use feh for setting wallpaper, which can take an image from the internet. Previously, I used a static list that I collected myself, but I recently discovered a wallpaper API from wallhaven.

Let's say, I want to get a space image as wallpaper, I would use this command:

curl -s 'https://wallhaven.cc/api/v1/search?q=space&ratios=16x9&sorting=toplist'

Where q is the query, ratios is the image ratio so that it fits the screen, and sorting is the way the results would be sorted before pagination. The results is long, but it looks like this:

{
  "data": [
      {
      "id": "l3zmwy",
      "url": "https://wallhaven.cc/w/l3zmwy",
      "short_url": "https://whvn.cc/l3zmwy",
      "views": 67050,
      "favorites": 705,
      "source": "https://www.artstation.com/artwork/YaQwgP",
      "purity": "sfw",
      "category": "general",
      "dimension_x": 1920,
      "dimension_y": 1080,
      "resolution": "1920x1080",
      "ratio": "1.78",
      "file_size": 781731,
      "file_type": "image/jpeg",
      "created_at": "2021-05-18 19:26:23",
      "colors": [
        "#424153",
        "#000000",
        "#663300",
        "#996633",
        "#999999"
      ],
      "path": "https://w.wallhaven.cc/full/l3/wallhaven-l3zmwy.jpg",
      "thumbs": {
        "large": "https://th.wallhaven.cc/lg/l3/l3zmwy.jpg",
        "original": "https://th.wallhaven.cc/orig/l3/l3zmwy.jpg",
        "small": "https://th.wallhaven.cc/small/l3/l3zmwy.jpg"
      }
    },
    ...
  ]
}

So, to get the image path from this, I run:

curl ... | jq -r .data[].path | shuf -n 1 | feh --bg-center

I use shuf because I'd like a new wallpaper every time I run this script. Put this as the startup script or add a cron job and you'll have a changing wallpaper. Disclaimer: this script does not always work, because of feh. If you're using GNOME, for example, feh can't be used to set background.


Footnotes
  1. Yes I know it's no longer free software (it still partially is I think). I have no choice, and I would still be more at peace with a source available software that is self-hostable.

  2. Copyright 2021 OpenSearch contributors. Released under Apache License.

https://xrvs.net/log2021-06-13-jq
Paperwork
Show full content

So during the last months, I have been dealing with paperwork for my school-credit internship in an external company. It's work involving... paper. Having to run around asking for signatures from the parties involved is certainly not a fun task, especially in this pandemic. It distracts me from the works I'm supposed to do---not that I don't have enough distractions.

Last Thursday I received a call because I got one part wrong in the agreement, which frustrated me a lot. This made me regret choosing to have my internship in an external company---my uni also provides unpaid internship for students, which requires less paperwork. I made this decision mainly because of peer and family pressure and financial concerns1---I was not willing to work for someone else for months without compensation2. On the bright side, at least now I know I should avoid complicated paperwork for the sake of my sanity (or at least that's what I tell myself as consolation).


Footnotes
  1. On that note, it's not that much, not worth the struggle. And I have not received a single cent, also.

  2. To be fair, all these school years is working without compensation. I should have considered it just a course...

https://xrvs.net/log2021-06-06-paperwork
Recommendation: Custom CSS with Stylus
Show full content
No more theme-switcher

So, I dropped the theme switcher to auto theme, which means it will switch theme automagically according to your system setting.

However, if one applies hardening to Firefox, one will notice that the feature is broken, and you'll see light theme by default. How would one fix this?

I have a nice solution: Stylus.

Stylus is a browser addon that allows you to write custom CSS. And of course, it is free software, released under GNU GPL version 3.0! And I love their privacy policy:

Unlike other similar extensions, we don't find you to be all that interesting. Your questionable browsing history should remain between you and the NSA. Stylus collects nothing. Period.

Applying to my website

I've customized my theme to look a bit like my vim theme, badwolf, but with a lighter background:

badwolf-ish theme

Here is the CSS I used:

:root {
    --font-color: #eee;
    --bg-color: #212121;

    --link-color: #0a9dff;
    --link-state-color:#ffa724;
    --link-state-border-color: rgba(238, 54, 54, 0.5);

    --thead-bg-color: #343a40;
    --table-border-color: lightgrey;

    --nav-bg-color: #242424;
    --nav-link-color: #b6b6b6;

    --pre-color: #333;
    --pre-bg-color: #f1f1f1;

    --bq-color: #ccc;
    --hr-color: #333;

    --pagination-bg-color: #373737;
    --pagination-link-color: #b6b6b6;

    --post-info-color: #599ada;

    --switcher-color: #333;
    --switcher-bg-color: #fff;
}

body {
    font-family: monospace;
}

h1, h2, h3 {
    color: #aeee00;
}

ul li {
  list-style: '* '; /* Remove default bullets */
}

ul li::marker {
    color: #ffa724;
    font-weight: bold;
}

The source file could be found on sourcehut

https://xrvs.net/log2021-05-30-css-stylus
[Announcement] This site is moved to huyngo.envs.net
Show full content

Short announcement: I'm moving my blog to envs.net for now. Reasons: SourceHut is going to require payment for build service 1 starting from June 2021. I have nothing against this decision -- it makes sense, and SourceHut is going to be paid-only2 service anyway. In fact, I'm planning to pay for SourceHut when I get financially independent. But for now, I am not, and furthermore I am not actively using sourcehut3, so paying is not financially wise.

On a slightly related note, I still have not figured out my financial plan in the future. I'm not that good at programming, so I don't expect myself to write some software so good that I can live on donation. That means I will have to work in some company, which means I'd likely have to write non-free software, or software I'm not enthusiastic about, both of which I find awful to think about.


Footnotes
  1. No, I don't need build service for pages.sr.ht, but I don't like manual upload using token.

  2. After alpha stage, it is only free of charge for contributors. I'll also change my code host service, if I still find myself unable to pay. It doesn't make much sense to pay to just host some config files and personal website, but maybe I'm a bit entitled.

  3. I'm currently having to deal with a school-credit internship, so I don't have time and mental energy for writing free software.

https://xrvs.net/log2021-05-29-move-site
Emulate system theme with firefox's devtools
Show full content

Firefox's devtool provides you with a tool to emulate dark/light mode (probably Chrome does, too). It can be useful for front-end developers when they want to design a system-based theme switch.

My default theme, light theme After choosing dark mode emulation

https://xrvs.net/notesdevtool-theme
Designing web for accessibility
Show full content

Accessibility means as many people as possible can easily read the content. In a narrower sense, this is specifically for people with some disabilities, such as blindness or deafness. In this post I mainly discuss the wider sense, which also includes the narrower sense, but as I don't usually use technologies specifically for them, I can't affirm how effective my approach can be.

Why is it important?

If you are a content creator, certainly you would want your content to reach as many people as possible. From an ethical point of view, it is unfair to people who cannot access your content because you have, to put it lightly, shitty design.

It is not only disabled people who benefits from accessibility. These people also benefit from it:

  • people with poor internet access
  • people who use old systems that don't support newer browsers or is not powerful enough to handle your load
  • people who use text browser like lynx or elinks
  • people who cannot use mouse or keyboard for some reason
  • people who use mobile devices (the most common case)

I have personally experienced difficulties in all the above five cases.

How to do it Semantic HTML

Semantic HTML is HTML elements used with its intended purpose. Don't use <div> for paragraphs. Don't use <button> for links. Don't use <links> for buttons.

That helps both screen readers and text browsers to render contents correctly.

Semantic HTML also helps SEO, which makes your content more findable1.

Less is more Less unnecessary media

Can this logo be done as a simple vector image instead of a full HD 8K png?2 Can it be simply just text?3

Do you really need that image background? Large images are not only heavy, but it's also distracting from the actual content.

Do you really need a cover image for each [blog] post? (I hate the fact that most WP themes promote this)

Of course, images and videos have their place, just don't spam it and keep it mind that it prevents someone from loading your website.

Less JavaScript

Making your website dependent on JS is a big no-no. There is a reason "JS bad" is a thing.

There are people who block JS -- text browsers and screen reader clearly don't support it. There are a larger number of people who intentionally block JS, because of privacy concerns -- JS can do nasty things fyi. Nonstandard JS API not supported by browser is even a more commonplace.

Less options in the navigation bar

Too much navigation is bad navigation. I have a terrible experience (two, actually) using only my keyboard for browsing the web with too much navigation at the top of the page. The first time because I was plugging a USB Drive and my desktop only have 2 slots, and the second time because I didn't install a graphical interface and use a text browser on terminal instead.

Try it: search something on Google (the only time I recommend it, for science 😀), using a graphical browser. Now use only your keyboard to get to the first result (use arrow keys and Tab). Count how many time you have to tab until you finally reach it. You would even find out navigations you didn't know existed!

But at least Google in text browser is not that bad. GitHub is much worse: It has more than two pages for just navigation! Compare that to SourceHut, on which the main content already appears in the first page.

Don't put ads!

Privacy concerns aside, ads is terrible for accessibility:

  • It distracts and interrupts readers from the main content.
  • It puts extra data to load for users with slow internet.
  • It discourages people from reading your content.

There are some websites that block adblockers. I don't have enough words to describe how immoral that decision is. That sometimes not only block people using adblockers, but also people who don't use JS (see above). I would recommend everyone to put such websites on block list so they won't waste any more bandwith with them.

Further reading

There are too much bad practices nowadays I can't cover within a post. I found these websites helpful to learn to avoid these bad practices:

  • HTMHell: a collection of bad HTML practices that are detrimental to accessibility) and how you can improve it.
  • MDN - Accessibility

Footnotes
  1. https://developer.mozilla.org/en-US/docs/Learn/Accessibility/What_is_accessibility

  2. Vector images are not necessarily lighter than pixel images, but if the image consists of simple shapes like circle or ellipse, it's likely that it is lighter.

  3. That makes your logo visible from text browser as well!

https://xrvs.net/log2021-05-01-accessible-web
TIL: Paste filename in vim
Show full content

To paste the current file's name into itself, type Ctrl+R then %.

https://xrvs.net/notesvim-paste-file
Formatting JSON Output with jq
Show full content

TIL: Syntax-highlighted JSON output

If you have some command that return (long) JSON, you can view it formatted with color with:

<command> | jq -C | less -r
https://xrvs.net/notesjson-output-format
Enough for first name/last name BS
Show full content

I keep seeing registration where I have to input my "First name" and my "Last name". What's the point? Isn't the user's full name just enough, if it's even needed?

Why you should avoid that

It's blatantly ignorant!

It assumes one must have a first name and last name to begin with. It is not the case for some country, such as Indonesia, where one can have just one name (also known as "mononym"). How are they supposed to fill your form?

Those who do this often assume that given name (i.e. "first name") always comes first, and family name (i.e. "last name") comes last and display as such without asking. In many Asian countries, like China, Vietnam, or Korea, given name comes after family name. They also do this in Hungary. When they allow changing name order, they add a comma in between, also without asking.

It is also not universally agreed upon where the middle names should go to. This is not a problem when the full name is not required (e.g. Facebook or Google account) but when full name is required (e.g. my school G-Suite account or for IELTS registration), it is problematic how they represent my name. A few lecturers whom I'm not close with keep referring to me by my middle name due to how Google displays my name.

Who do this?
  • Many web services, e.g. Facebook, Google, LinkedIn
  • Language certification tests: IELTS, TCF, DELF/DALF, ...
Proposed alternative
  • Just ask for full name. Governments do that and they're fine.
  • Ask how users want to be referred as, don't assume. I never want to be referred by my family name.
  • Maybe avoid asking for real name at the first place if you're just some online service. Real name policy harms social network users. Even Google dropped that BS.
https://xrvs.net/log2021-02-08-naming
Recommendation: Using openring to add blogs you follow
Show full content

You may notice that now my blog now has a new section near the footer: a list of articles from blogs I follow. This is generated by openring, a tool that read a list of RSS feeds and generate these.

I found out about this when reading Drew DeVault's blog (who created openring). I think it is a nice way to endorse authors we want to support and share cool things we read to our audience.

In this blog, I will write a tutorial to use this with jekyll.

Install openring

I am not aware of any prebuilt packages for openring, so let's build it from source.

Install dependencies

Openring depends on golang. This works on go1.14, the latest version on Tumbleweed repository, but I recommend installing the latest version from golang.

You can refer to golang's installation instruction for details.

Build from source

Firstly, clone the repository:

git clone https://git.sr.ht/~sircmpwn/openring

Next, simply build the packages and link it to /usr/local/bin so that it can be run:

go build -o openring
sudo cp openring /usr/local/bin/
Customize looks

From openring's README:

This is a tool for generating a webring from RSS feeds, so you can link to other blogs you like on your own blog. It's designed to be fairly simple and integrate with any static site generator. The basic usage is:

openring \
  -s https://drewdevault.com/feed.xml \
  -s https://emersion.fr/blog/rss.xml \
  -s https://danluu.com/atom.xml \
  < in.html \
  > out.html

The in.html is a template whence openring generate the HTML for the feed.

I copied the template from DeVault's blog (don't worry, it's MIT-licensed), with a little modification:

  • I wrap it in a div.wrapper. The wrapper class is a class in minima theme that limit the max width for readability and auto-collapse on smaller devices.
  • I use footer-col for each class. Since this is also styled by minima, I don't have to worry about it.
  • I added a thin border around each article with the following sass (also modified from DeVault's blog)
---
---

.webring {
  margin-bottom: 1rem;
  .attribution {
    float: right;
    font-size: .8rem;
    line-height: 3;
  }

  .footer-col.article {
    padding: 0.5rem;

    margin: 0 0.5rem;
    border: 0.01rem solid #333;
    @media(max-width: 640px) {
      margin: 0.5rem 0;
    }   
  }
}
Future works?

Currently, I generate the feed manually when I update my blog. This probably is not good enough if I want the webring to be updated even when I'm not active? A cronjob could solve this problem, but I'll left it as an exercise to the reader ;).

https://xrvs.net/log2021-01-11-openring
Moving away from Big Brother(s)
Show full content

Due to a recent event, people have been actively moving away from it to Telegram1 or Signal. This is just the first step of moving away from Big Brother's surveillance.

My personal story

Though I have never used WhatsApp, I myself have been using Facebook for quite a long time (about 8 years or so) and so do people around me. It is worrying that Facebook (along with Zalo, a popular messaging service in Vietnam) has become a de facto main communication channel between schools and students/parents and among family members.

It is hard to switch 100% to another service: people are too familiar with it, and are already connected to their "friends"2. They are reluctant to switch to something new, which includes choosing an unknown service and probably a client, creating a new account3, and adding people there. And if no one is moving, you can't really move either.

I haven't used Facebook or Instagram for about two weeks, and I'm certainly happier - I don't have to deal with the negativity from Facebook drama or typical Facebook user hostile behaviors (my fault for joining so many groups). I still have to use Messenger, though, since my friends refuse to use matrix. I will delete that soon enough, along with my Facebook account.

Things to do

Deleting Facebook account probably does not keep you entirely from Facebook's greed, but it sure is a great first step: you thereby declare your privacy and that you do not allow them to collect your data.

Facebook can probably track you via other means, such as ads, or embedded comments on other websites. To avoid this, block trackers with some extension such as Ghostery or Privacy Badger. It also blocks tracking ads from Google.

I also recommend LibreJS, a browser extension by GNU that blocks nonfree JavaScript. These scripts can unknowingly send data to the server as well. However, since not everyone is aware of GNU's guidelines for licensing JavaScript, it may blocks some free JavaScript as well (example being that of Element and Hydrogen being blocked).

Facebook is not the only Big Brother that keeps surveilling you: on top of my head, there are Microsoft, Google, and Apple. Unfortunately, they stand behind ones of the most popular operating systems for a lot of devices, namely Windows, MacOS, Android4, and iOS. This gives them a lot of power over a majority of users - they can easily listen to every process you run, every file you download or upload. Computer users should switch to a GNU/Linux distros of their choice for their own good. For mobile phone, you can try installing Replicant (Android replacement) or buy a GNU/Linux mobile phone (e.g. PinePhone or Librem), though I cannot confirm their ease of use, for I haven't tried.

It doesn't stop there: they also own many services that may pose privacy concerns or censorship whose replacements can be found below:

  • YouTube: PeerTube is an alternative, though it can be slow if there is no peer near you. Invidious and NewPipe are free clients that let you watch YouTube videos without ads (i.e. Google's trackers). Don't mind about "supporting them" with ads - it's not worth it - you can support them by donating them some money instead.
  • Google search: I'm not aware of a decent free search engine, but DuckDuckGo and Ecosia claim they respect user's data and do not store nor sell them
  • Google translate: if you're a language learner like me, I strongly suggest looking up in dictionary instead.
  • Google Map: I'm not aware of a good alternative, please suggest some
  • GMail: tons of mail providers are out there, it's not hard to find one. The only problem is that so many services allow you to sign in with GMail that you probably are dependent on it by now.
  • GitHub: git hosting is very common. I use disroot and source hut, but there are others such as gogs or GitLab. Even though I'm not aware of tracking via GitHub, there has recently been an incident proving its untrustworthiness.
  • LinkedIn: again, I'm not aware of free alternative for the same purpose, though I didn't find LinkedIn useful myself.
Footnotes
Footnotes
  1. Telegram's client is open source; its server and protocol are probably not.

  2. For many people, this is acquaintance plus or minus family.

  3. Creating a new account should be a simple process, but for many people, especially the elderly, find this troublesome and have to rely on their children for it.

  4. Even though Android itself is free, it contains many nonfree parts, and mobile phones running on Android are often shipped with Google Play Service (and/or sometimes the surveillance system of the manufacturer)

https://xrvs.net/log2021-01-09-big-tech
Giving up on WordPress!
Show full content

For the last week, I was trying to setting up my own WordPress instance, since I have access to a server and use a free domain name. It was such a hassle.

Why I want to start a blog

Writing is a way of polishing my writing skills. For so long, all what I write has been only code. Now that I'm close to my graduation, writing long text suddenly becomes important to me - I need to write reports and after that my thesis.

It is also a way of expressing myself. Social media is a shorter version for this, and they may attract more interactions from people you know, but blog on the other hand helps you express your idea and opinion publicly. They're also much more organized in the sense that my list of posts does not contain shares from other users, or that I can use hashtags to categorize my posts.

Choosing WordPress

At first I was thinking of using wordpress.com, since it's already hosted, but then I realized they can put ads on my blogs without my consent (well, technically, they would have my consent if I used their service). On top of that, I always prefer self-hosting whenever possible: I have all control (doubt) over what is running and what not. I would not what a third party to track my readers or showing them ads on my blog.

The server was running on Ubuntu 16.04. That is a rather old one, so it doesn't support latest packages, including Apache, PHP, and WordPress (they do have WordPress in the Ubuntu repo), and I dare not upgrade it.

On the other hand, I already hosted some other services with Nginx there, so I'd prefer to use Nginx instead of Apache. They do have that option, but alas, those configs file seem long and I was not sure if I would break something while editing the nginx.conf file.

I did try to use Nginx as a reverse proxy to my Apache server, though. It did work, but there is another problem, which I will talk about in a next section

Using Docker

I also tried another option - docker. I wondered why I didn't think of it earlier.

I have recently been using docker quite regularly, e.g. for CouchDB and RethinkDB which aren't packaged for my distro, or SQLServer and MongoDB, which were required for my database course. Docker is a way to easily run some program in a contained environment with simple configuration that can be input from command line or docker-compose file. Most importantly, they're confirmed to work.

And nice, it did run.

Now I need to set up a domain name for the blog.

Port forwarding

For personal reason, I'd rather not reveal my domain name here, so I'd use example.com in its place.

I signed up a domain name at freenom, and it only took me one day until the domain name is fully available.

The only problem is that example.com always show me a bad gateway response. I made sure my Nginx configuration were right, but somehow it weren't listening on port 80 - the default port for HTTP. I had to go to example.com:12345 (the port I ran my docker on), and for normal users that doesn't look intuitive at all.

It turned out, I didn't link my configuration correctly, so there are broken symlinks in /etc/nginx/sites-enabled/ and the configuration was never included.

After I fixed that, it worked nicely.

Different name == broken website

WordPress uses absolute path (example.com/blog/something) rather than relative path (/blog/something). This leads to a problem: if I want to migrate to another domain name (guaranteed to happen, since I use a free domain name that will expire eventually). I discovered this when I moved from a port to different port, and from host IP to domain name. It frustrates me, but it isn't a big problem for now.

Disappointing experience

I did successfully hosted my own WP instance, but it didn't feel right for me.

WordPress is so bloated. They target non-programmer, and they have a lot of drag-and-drop editing. It is hard for me to select the right block and edit it. They have a lot of features for images and widgets that I won't use. Of course I can remove those features, but they would take time and not is worth it.

As a result, I'm refrained from posting there.

Write.as

Then I was recommended this page on Mastodon. It feels so right to me: as a programmer I love writing with Markdown - the markup language is so easy to write, and it allows me to write quickly. I can also write it in a preferred text editor such as vim and paste the text here.

I did try to host my own WriteFreely instance (the technology behind write.as), but since I invested too much energy for WordPress I don't feel like it now, even though it seems simple according to the installation instruction. The docker image is also not at all documented.

Fortunately, write.as already feels right to me - no ads, and tracking is easily opted out. The only problem here is that my domain name is wasted there.

Update

Well, I just installed (it was actually easy!), and now I've moved to divers.ml

New update: I just tried jekyll, and it's awesome. Now my blog is hosted on GitHub. I guess I will leave the domain unused.

https://xrvs.net/log2021-01-02-wordpress