Igor Bubelov https://bubelov.com/ All posts by Igor Bubelov Empires: The Greeks - Crucible of Civilization https://bubelov.com/blog/2026/greeks/ Thu, 12 Mar 2026 00:00:00 +0000 https://bubelov.com/blog/2026/greeks/ <p>It&rsquo;s a solid documentary on ancient Greece. It describes the origins of a few modern things like the Olympics or marathon running, but the main focus is Athens and the conflict between the Persians and Greeks. Some early philosophers are also mentioned, so it&rsquo;s a pretty comprehensive overview of the period (~5th century BC) and the region.</p> The Myth of Free Speech https://bubelov.com/blog/2026/free-speech/ Fri, 06 Mar 2026 00:00:00 +0000 https://bubelov.com/blog/2026/free-speech/ <h2 id="preface">Preface</h2> <p>Most polities insist they possess free speech, pointing to their constitutions as proof. But the devil, as always, is in the details.</p> <p>The boundaries of acceptable discourse can be enforced at different levels, so it doesn&rsquo;t have to be the state alone. People may be forced to self-censor in their workplaces, or they might fear that a group of thugs will beat them up if they dare to voice something controversial.</p> <p>To fully grasp free speech, one must start reading history, or at least the headlines (MLK, or even the assassination of Charlie Kirk as a recent example).</p> <h2 id="continental-free-speech">Continental Free Speech</h2> <p>European nations have never been known for a high tolerance for disagreeable speech.</p> <p>Post-WW2, countries like Germany and France built their identities on the concept of &ldquo;militant liberal democracy&rdquo;. Freedom, in their narrow definition, must be protected from its enemies. This created an environment where speech is a privilege granted by the state, revocable at will.</p> <p>Some would call it hypocrisy. When confronted, the average citizen falls back on the tired trope, repeating &ldquo;hate speech is not free speech&rdquo; nonsense.</p> <h2 id="american-ideal">American Ideal</h2> <p>The First Amendment is real and it has consequences, unlike the paper promises of Europe. It&rsquo;s one of the best products of the Enlightenment. For two centuries, it has protected many good people alongside some questionable gentlemen much of the world would happily jail. The most notorious examples are Nazis marching in Skokie, flag burners, and creators of art perceived as degenerate or socially harmful.</p> <p>But the constitution can only get you so far. There are many things you can say in the US without serious legal consequences, but there are clear red lines you aren&rsquo;t supposed to cross.</p> <h2 id="reality-check">Reality Check</h2> <p>To see the real limits of speech, we must go back to Thomas Hobbes, the foundational thinker on human nature.</p> <p>In his masterpiece, Leviathan, he saw man as a fundamentally vulnerable creature. His state of nature assumed total equality in that regard. The weakest person has the strength to kill the strongest, either by &ldquo;secret machination&rdquo; or by confederacy with others. This universal capacity for violence is the basic foundation of the state. We surrender our rights to the Leviathan, the state, hoping to build deterrence against the war of all against all.</p> <p>In my view, this is absolutely true, and it is also the most honest limit on the freedom of speech. Yes, the United States government will not arrest you for saying something provocative. But that legal guarantee does not magically disarm a guy who didn&rsquo;t like what you said.</p> <p>This is the real limit. The history of the American South during the Civil Rights Movement proves this point. Black citizens had the legal right to register to vote and sit at lunch counters, but the exercise of that right had to deal with the possibility of lynchings, bombings, and firehoses. The law was useless, and that&rsquo;s the cruel Hobbesian check on liberty.</p> <p>It means that no one can truly escape self-censorship. Some may call it cowardice, but it&rsquo;s the rational recognition that we are all equally vulnerable to the violence of others.</p> <h2 id="conclusion">Conclusion</h2> <p>Freedom of speech can only be taken seriously in a very narrow scope. There are no absolutes, only degrees of risk. The best we can hope for is the freedom to criticize our government without being disappeared. That is a monumental achievement of the Enlightenment, and it should not be dismissed.</p> <p>But the myth of absolute freedom contradicts human nature. You can have the right to speak, but you cannot have the right to be heard, nor the right to be safe from the consequences of being heard. If you irritate someone enough, they will always have the option of ending your life, ruining your career, or destroying your reputation.</p> <p>Speak freely, if you must. Just don&rsquo;t expect to speak safely.</p> BTC Map February Recap https://bubelov.com/blog/2026/btcmap-02/ Sun, 01 Mar 2026 00:00:00 +0000 https://bubelov.com/blog/2026/btcmap-02/ <p><strong>Note</strong>: this only reflects my personal work this week. Visit the <a href="https://blog.btcmap.org/">BTC Map Blog</a> for consolidated monthly reports.</p> <h2 id="infra">Infra</h2> <h3 id="backup-server">Backup Server</h3> <p>BTC Map infrastructure is hacky, mostly because we lack contributors with strong ops skills. I handle most maintenance, but I&rsquo;m no expert, and I tend to prioritize fun tasks over boring ones.</p> <p>Backups are boring, but you can&rsquo;t avoid them forever as the project grows. Our old backup system was too unreliable, so I finally forced myself to set up a dedicated backup server.</p> <p>This server regularly fetches critical data from the API server. While the API server is accessible to the whole team, this server is only accessible by me. That protects our backups if someone else on the team is hacked, but if I&rsquo;m hacked, it&rsquo;s game over.</p> <p>Not ideal, but better than the status quo, and the places themselves are hosted on OSM with an excellent decentralized backup system. More people with API server access should set up their own backups to improve the new setup, and I&rsquo;m planning to help with that.</p> <h3 id="ansible">Ansible</h3> <p>Our servers are undocumented. That&rsquo;s something we&rsquo;ll need to fix long‑term. I started drafting Ansible configs to slowly document our existing fleet. It already partially covers two servers, but there&rsquo;s a lot of work ahead.</p> <h2 id="website">Website</h2> <h3 id="merchant-page-speedup">Merchant Page Speedup</h3> <p>The merchant page is one of the most‑used in the web app, and its performance was abysmal due to a full API sync dependence. A full sync means fetching up to a million JSON objects, which is not optimal for a random visitor with no pre-existing cache.</p> <p>I&rsquo;m not sure why it relied on a full sync, probably legacy UI design decisions that didn&rsquo;t account for API capabilities. I added a few new endpoints to break away from the sync, leading to orders‑of‑magnitude performance improvements for cold clients. Things that often took minutes now load in seconds or less.</p> <h3 id="events-map">Events Map</h3> <p>The events map is still an unmerged draft, but it could be an important step for BTC Map web app (Android app has it already). In many regions, Bitcoin events and meetups are more popular than merchants, and there&rsquo;s no good source for event data.</p> <p><a href="https://github.com/teambtcmap/btcmap.org/pull/757">https://github.com/teambtcmap/btcmap.org/pull/757</a></p> <h2 id="android">Android</h2> <p>Preview builds are available here: <a href="https://github.com/teambtcmap/btcmap-android/releases/tag/preview">https://github.com/teambtcmap/btcmap-android/releases/tag/preview</a></p> <h3 id="improved-clustering">Improved Clustering</h3> <p>The new clustering approach offers a smooth user experience, but it required significant refactoring and testing. It finally feels stable enough to release, I&rsquo;m planning to roll out a new Android app version next month.</p> <p>A nice side effect: map rotation is back. The old clustering didn&rsquo;t support it, so I had to disable it.</p> <h3 id="website-styles">Website Styles</h3> <p>Custom user themes work fine for normal widgets, but the map isn&rsquo;t a normal widget as it can introduce contrast issues. Some people also use light map styles in dark mode and vice versa.</p> <p>The new Android app uses well‑tested website colors by default, which work well in both light and dark modes. I tested them with all supported map styles and liked the results.</p> <p>You can still opt into custom Material colors through settings, or set your own. The Android app is now the most customizable Bitcoin map out there.</p> <h2 id="api">API</h2> <h3 id="logging">Logging</h3> <p>Our API logs all requests for analytics and debugging. That data isn&rsquo;t connected to user accounts (users don&rsquo;t even have accounts), and old records are auto‑erased, saving storage costs and ensuring we don&rsquo;t keep permanent records.</p> <p>The new logging system is much faster and saves additional fields like user agent. That&rsquo;s handy for debugging, the latest example was accidentally banning our own e2e test runner for API abuse.</p> <h3 id="og-cache">OG Cache</h3> <p>OpenGraph images are what you see when sharing links. For BTC Map merchants, you&rsquo;ll see a map with a pin in the middle. These images are generated on the fly by our API, but they rely on OSM tile servers, and we don&rsquo;t want to abuse their generosity.</p> <p>The new cache ensures we only ask OSM servers once per merchant, reducing load on their infrastructure and improving our own latency.</p> <h3 id="new-database-structure">New Database Structure</h3> <p>BTC Map&rsquo;s database now consists of three simple files:</p> <ul> <li><code>main.db</code></li> <li><code>images.db</code></li> <li><code>log.db</code></li> </ul> <p>The names are self‑descriptive, and the structure makes our data portable. For example, the backup server can just use <code>sqlite3_rsync</code> on these files for incremental backups.</p> <h3 id="fetching-place-areas">Fetching Place Areas</h3> <p>This endpoint is handy when showing a BTC Map location and you also want to show which communities or country it belongs to.</p> <p><a href="https://api.btcmap.org/v4/places/20423/areas">https://api.btcmap.org/v4/places/20423/areas</a></p> <h3 id="fetching-place-activity">Fetching Place Activity</h3> <p>This endpoint adds optional context to places, useful for third‑party apps. Currently, it shows core lifecycle events (created, updated, deleted). I plan to add &ldquo;commented&rdquo; and &ldquo;boosted&rdquo; events in the future.</p> <p><a href="https://api.btcmap.org/v4/places/8660/activity">https://api.btcmap.org/v4/places/8660/activity</a></p> <h2 id="developer-portal">Developer Portal</h2> <p>Since BTC Map clients don&rsquo;t support user accounts, some sensitive operations can&rsquo;t be done anonymously. We do have an auth system, used by moderators and third‑party integrations.</p> <p>Onboarding new accounts has always been hard, so I created a simple website that automates signups and API key management.</p> <p><a href="https://developer.btcmap.org/">https://developer.btcmap.org/</a></p> <h2 id="conclusion">Conclusion</h2> <p>It was a productive month. I&rsquo;m especially proud of improving our fundamentals: cleaning up API data and dramatically speeding up the merchant page.</p> The Moral Universe in the Pre-Socratics (2/81) https://bubelov.com/blog/2026/philosophy-2/ Sun, 01 Mar 2026 00:00:00 +0000 https://bubelov.com/blog/2026/philosophy-2/ <p>This post is based on the notes I made watching identically named Artur Holmes lecture in his excellent History of Philosophy course.</p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#pre-theology">Pre-Theology</a></li> <li><a href="#anaximander-610-546-bc">Anaximander (610-546 BC)</a></li> <li><a href="#pythagoras-570-495-bc">Pythagoras (570-495 BC)</a></li> <li><a href="#moira">Moira</a></li> <li><a href="#cosmic-justice">Cosmic Justice</a></li> <li><a href="#nature--city-state--moral-life">Nature &ndash;&gt; City State &ndash;&gt; Moral Life</a></li> <li><a href="#logos">Logos</a></li> <li><a href="#the-opposition">The Opposition</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="pre-theology">Pre-Theology</h2> <p>Pre-Socratic thought demonstrates the gradual shift from a mechanical and chaotic universe to a moral and ordered one, which opens the door to theology. We can see similar questions, concepts, and a tendency towards a unified explanation of reality.</p> <h2 id="anaximander-610-546-bc">Anaximander (610-546 BC)</h2> <p>Anaximander provides the clearest early example of moral cosmology. He wrote that things &ldquo;give justice and reparation to one another for their injustice according to the order of time,&rdquo; which is a pretty convoluted way of saying that the cosmos is <strong>not</strong> morally neutral. No act of overstepping is left without consequences.</p> <p>Things exist by arising from the apeiron (the boundless), and they eventually return, paying the penalty for their separation. This is basically a legal and moral framework applied to physics.</p> <p>The universe, for Anaximander, has a built-in ethical structure. It is an impersonal cosmic justice that anticipates the later personified Dike of Greek tragedy and the Stoic Logos.</p> <h2 id="pythagoras-570-495-bc">Pythagoras (570-495 BC)</h2> <p>The closest one to theology though is Pythagoras. He believed in immortality and transmigration of the soul (metempsychosis). The soul is punished or rewarded by being reincarnated into different life forms based on its conduct.</p> <p>There is a moral order to the universe that governs the fate of individual souls. Justice is cosmic and personal. It introduces the idea of a moral universe that judges and cycles souls, which is a concept that later influences Plato and, indirectly, Christian theology.</p> <h2 id="moira">Moira</h2> <p>In Homer and early Greek poetry (8th–7th century BC), the universe is far from being moral. It is governed by Moira, usually translated as &ldquo;fate&rdquo;, &ldquo;portion&rdquo;, or &ldquo;share&rdquo;. Even the heroes who are &ldquo;good&rdquo; suffer and die because their portion is suffering and death. There is no guarantee that virtue leads to happiness or any good outcome.</p> <p>Beauty, wealth, status, and honor are what make a Greek hero. There is no interest in abstract morality.</p> <p>The idea of cosmic justice is a radical departure from that status quo.</p> <h2 id="cosmic-justice">Cosmic Justice</h2> <p>The core of that idea is a belief that the universe is not morally neutral. It has a built-in ethical structure that ensures balance, proportion, and eventual punishment for any violation of its order. Sort of a moral thermodynamics.</p> <p>Everything in the cosmos has its proper place, measure, or limit. The sun has a path it must follow, seasons have their duration, creatures have their lifespans. This is the &ldquo;order&rdquo; part.</p> <p>To &ldquo;overstep&rdquo; these boundaries is an act of injustice (adikia). This applies to natural phenomena as much as to human actions.</p> <p>Justice (dike) restores balance, it&rsquo;s a reversion to the mean. The transgressor must pay penalty or give reparation. This is a built-in inescapable restoration of cosmic order.</p> <h2 id="nature--city-state--moral-life">Nature &ndash;&gt; City State &ndash;&gt; Moral Life</h2> <p>Since the natural world is believed to be ordered, and we are all tied to it, it becomes the foundation of being. Order is nature, and nature is order. It&rsquo;s not something you can escape from.</p> <p>Greek city-states were perceived as microcosms, so it&rsquo;s a natural way to explain why city life needs a moral order and a justice system of some sort. Each city had distinct elements, such as classes and factions, which were considered key components that needed to exist in a certain balanced proportion.</p> <p>Political philosophy is born from cosmology. The question &ldquo;What is the best government?&rdquo; is really asking &ldquo;What form of human order best reflects the order of reality?&rdquo;.</p> <h2 id="logos">Logos</h2> <p>The Pre-Socratic concept of Logos (particularly in Heraclitus) is a rational principle that ensures cosmic order. It guarantees that, in the end, things work out justly. Wisdom, then, means speaking the truth and acting according to nature. Measure, balance, and harmony give order, value, and meaning to life.</p> <h2 id="the-opposition">The Opposition</h2> <p>The main opposition to the idea of a moral universe came from the Sophists, who were mostly traveling teachers active in the late 5th century BC (~450–400 BC). They were a loose group, but they shared certain tendencies: anthropocentrism, skepticism, and a focus on pragmatic tools such as rhetoric.</p> <p>The most famous statement of Sophist philosophy comes from Protagoras (490-420 BC):</p> <blockquote> <p>Man is the measure of all things: of things that are, that they are; of things that are not, that they are not.</p> </blockquote> <p>This is quite a sceptical take, implying that values are human inventions and the truth is relative. In general, Sophists were very &ldquo;real&rdquo; and you can see their intuitions in modern and widely accepted concepts such as realpolitik.</p> <h2 id="conclusion">Conclusion</h2> <p>The Pre-Socratic period created an ideological base and a set of key areas of focus for the whole western philosophical project. The cool thing about philosophy is its &ldquo;durability&rdquo;, you can still make the same arguments people invoked during those early debates, and hardly anything has been settled.</p> <p>Our society underwent radical change during the 20th century, but that doesn&rsquo;t mean our worldview is that different from the people who lived thousands of years ago. People who say that philosophy is mostly useless should at least acknowledge its great work of distillation, separating the ephemeral from the eternal.</p> Gold vs Bitcoin https://bubelov.com/blog/2026/bitcoin-vs-gold/ Wed, 25 Feb 2026 00:00:00 +0000 https://bubelov.com/blog/2026/bitcoin-vs-gold/ <h2 id="preface">Preface</h2> <p>It&rsquo;s satisfying to watch gold break price records since it means the old world order is under stress. But Bitcoin isn&rsquo;t following the same pattern. It&rsquo;s behaving more like NASDAQ on steroids, and there are good reasons these two contrarian assets are on different trajectories right now.</p> <h2 id="supply-and-demand">Supply and Demand</h2> <p>Gold still has clear advantages over Bitcoin, most notably: a well‑established legal framework, lower and more familiar security risks, and universal central bank adoption. Price is where supply meets demand, and central banks are creating enormous demand for gold while completely ignoring Bitcoin.</p> <p>Gold wouldn&rsquo;t be near these highs without central bank buying. Bitcoin would skyrocket if they ever added it to reserves, but there&rsquo;s no reason to expect that anytime soon.</p> <p>On the Bitcoin side, demand is driven by retail investors, buying directly or through ETFs. US‑based ETFs helped push Bitcoin above $100k briefly. Now we&rsquo;re at $65k, which means whales are cashing out, an expected post-pump behaviour.</p> <p>People are mortal. No one holds sats forever. If you were lucky enough to get some in 2009, in your thirties, you might be in your fifties now. Life is short, so it&rsquo;s hard to blame early adopters for treating themselves or diversifying.</p> <p>I don&rsquo;t expect selling pressure to ease, but I also don&rsquo;t expect buying pressure to disappear. Central banks remain unconvinced, yet Bitcoin still has enough ideological firepower to attract new retail investors, many of whom are disillusioned with the current system and have a long time horizon.</p> <p>Short‑term, sentiment matters more than fundamentals, but fundamentals decide the endgame.</p> <h2 id="scarcity">Scarcity</h2> <p>Gold is often called scarce, but its supply is elastic. If the price doubles, mining companies dig more. Bitcoin&rsquo;s supply is fixed and predictable, with new issuance cut in half every four years.</p> <p>Another important difference is permanence. Gold never truly disappears. If you die, your heirs inherit it. But lose your Bitcoin key, and those coins are gone forever. Every lost key permanently reduces the available supply, which benefits remaining holders, a feature gold doesn&rsquo;t have.</p> <h2 id="security">Security</h2> <p>Central banks have different security concerns than individuals. They rely on state power to protect their reserves. For an individual, securing physical gold is far more difficult. Bitcoin, stored properly, can be easier to protect and far easier to move across borders.</p> <h2 id="conclusion">Conclusion</h2> <p>I&rsquo;m happy to see gold pump. With Bitcoin sitting 50% below its peak, I believe its long‑term potential is far greater. For anyone curious about opting out of the old system, now is a good time to buy.</p> There is No Left and Right https://bubelov.com/blog/2026/there-is-no-left-and-right/ Sun, 22 Feb 2026 00:00:00 +0000 https://bubelov.com/blog/2026/there-is-no-left-and-right/ <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#definitions-matter">Definitions Matter</a></li> <li><a href="#organizations-matter">Organizations Matter</a></li> <li><a href="#bitcoin">Bitcoin</a></li> <li><a href="#echoes-of-cold-war">Echoes of Cold War</a></li> <li><a href="#the-problem-with-the-axis">The Problem With the Axis</a></li> <li><a href="#useful-idiots">Useful Idiots</a></li> <li><a href="#imperialist-values">Imperialist Values</a></li> <li><a href="#nationalist-values">Nationalist Values</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="definitions-matter">Definitions Matter</h2> <p>Before I join any debate, I ask people to define the core concepts we&rsquo;re about to discuss.</p> <p>Left and right are words everyone uses and no one can define. Ask ten people what they mean, and you&rsquo;ll get ten different answers.</p> <h2 id="organizations-matter">Organizations Matter</h2> <p>Ideas alone are useless without an organizing force. There are plenty of well‑known elite organizations that sometimes repeat left and right talking points to secure votes, but they serve their own interests, as we&rsquo;ll expand on later.</p> <p>When did the Democratic Party last deliver for its left wing? When did the Republican Party keep its promises to &ldquo;populists&rdquo;?</p> <p>You also can&rsquo;t map the Democratic Party onto any random European left‑wing party. Most Europeans would find the comparison mildly offensive, which tells us there&rsquo;s no global left or right, only local elites with local interests.</p> <p>Voters are presented with a small pool of pre‑vetted organizations that never fully deliver. Your predicted vote shapes what they say, but not what they do, unless you&rsquo;re actually part of the organization. Voting, in this sense, is mostly a performative ritual to justify the ruling elite.</p> <h2 id="bitcoin">Bitcoin</h2> <p>Bitcoin is a great example of a self-organized effort that transcends the left-right divide while achieving its goals through strong but leaderless organization.</p> <p>I visit many Bitcoin meetups, and there&rsquo;s no shortage of people who self-identify as left or right. What unites them is an anti-authoritarian stance.</p> <p>No powerful mainstream political organization is truly anti-authoritarian. Power is what they&rsquo;re after, and once they have it, they expand it rather than shrink it.</p> <p>Bitcoin communities are full of people getting things done, and organizing is a powerful way to transcend fake ideology and achieve meaningful change.</p> <h2 id="echoes-of-cold-war">Echoes of Cold War</h2> <p>The left-right divide peaked during the Cold War, but definitions were murky even then. Some used two axes where the USSR landed economic left, social right and the US landed economic right, social right.</p> <p>Since then, the US has moved all over. It shifted left under Roosevelt, right under Reagan, and left socially since the 60s. China went its own way with state capitalism and social conservatism. Neither fits a simple left-right axis anymore.</p> <h2 id="the-problem-with-the-axis">The Problem With the Axis</h2> <p>You&rsquo;ve probably seen the famous four-quadrant &ldquo;political compass&rdquo; that tries to place everyone from Stalin to Ayn Rand in a tidy bracket. They&rsquo;re funny because they fail, and the failure is the point. Every attempt to squeeze messy reality into two neat axes ends up exposing its own absurdity.</p> <p>Left-right doesn&rsquo;t work. Economic/social doesn&rsquo;t work. So is there a better way to make sense of what&rsquo;s actually going on?</p> <h2 id="useful-idiots">Useful Idiots</h2> <p>&ldquo;Useful idiots&rdquo; is a Cold War term that still explains a lot.</p> <p>The left is a loose set of ideas with no global organization, which is perfect for empires that need an all‑inclusive ideology. Local elites exploit left‑leaning masses for legitimacy while pursuing their own goals. I call them <strong>imperialists/globalists</strong>. The EU is a classic example, where bureaucrats push for endless expansion and federalization while ignoring local concerns.</p> <p>The right is equally fractured. Its isolationism and cultural purity talk are weaponized by <strong>nationalists/localists</strong> who never deliver. The MAGA movement plays this game, all rhetoric, no results. Don&rsquo;t expect real change since it&rsquo;s never the goal.</p> <p>Both sides feel affiliated with their elites, but elites only act when it tightens their grip on power. Globalists attack localists instead of delivering healthcare. Localists expose globalists but never deliver mass remigration or reform. The promises are the point and delivery isn&rsquo;t. What matters is expanding administrative control and creating more jobs for bureaucrats.</p> <h2 id="imperialist-values">Imperialist Values</h2> <p>Empires are big and strong, but they are vulnerable to internal conflicts. Historically, expansionist nations have ruled over dozens of ethnicities and religions, and most eventually collapse due to nationalism and separatism. This is why nationalists are mortal enemies of imperialists, and why the left-right divide is often just a proxy war between two elite factions competing to manage or exploit these internal fractures.</p> <p>There&rsquo;s a reason the US is so polarized. It&rsquo;s not organic at all. US rivals deliberately fuel those divisions because that&rsquo;s exactly where empires are vulnerable. You hit where it hurts.</p> <p>Another example of misframed imperialism is modern Russia. Abroad, Putin is framed as a nationalist strongman. Inside Russia, he&rsquo;s seen quite differently, more like Biden, with the same fixation on diversity and importing millions of immigrants without the state capacity to integrate them. The external image is nationalist while in reality he&rsquo;s just another imperial manager.</p> <h2 id="nationalist-values">Nationalist Values</h2> <p>Nationalist elites think that ethnically and religiously homogenous society is a safer bet. Empires are volatile, and not every nation can become one, since demographics, geography, and history all impose their limits. Right-wingers, in this frame, are mostly proxies of these organized nationalist groups.</p> <p>That said, nationalism isn&rsquo;t the default mode for elites. They turn nationalist when they don&rsquo;t see a path to imperial expansion, or when they need to resist an external threat. North Korea, Iran, and even China fit this pattern. Each has good reason to maximize internal cohesion since they&rsquo;re all targets of the biggest, meanest empire of our time.</p> <h2 id="conclusion">Conclusion</h2> <p>Framing both camps as equal, and equally Machiavellian, might come across as cynical. But I invite people to think about deliverables instead of ideas. Both left-wingers and right-wingers end up disappointed when their favored elite group gets power. That&rsquo;s the best evidence that they&rsquo;re being used and kept in the dark.</p> <p>Ideas are meaningless without organization. Good intentions will always be weaponized by elites to justify their rule. So when someone self-identifies as a leftist or a rightist, a good question to ask is: &ldquo;Which organization, and what did it do to advance your cause?&rdquo;</p> Do Central Banks Print Money? https://bubelov.com/blog/2026/do-central-banks-print-money/ Sun, 15 Feb 2026 00:00:00 +0000 https://bubelov.com/blog/2026/do-central-banks-print-money/ <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#the-common-narrative">The Common Narrative</a></li> <li><a href="#dual-mandate">Dual Mandate</a></li> <li><a href="#its-about-spending-covid-monetarism-vs-qe">It&rsquo;s About Spending: COVID Monetarism vs QE</a></li> <li><a href="#what-actually-happens">What Actually Happens</a></li> <li><a href="#what-if-the-fed-sold-all-its-bonds">What if the Fed Sold All its Bonds?</a></li> <li><a href="#fed-needed-qe-more-than-banks">Fed Needed QE More Than Banks</a></li> <li><a href="#helicopter-money">Helicopter Money</a></li> <li><a href="#who-really-prints-the-money">Who Really Prints the Money</a></li> <li><a href="#the-sovereignty-question">The Sovereignty Question</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="the-common-narrative">The Common Narrative</h2> <p>&ldquo;The Fed prints money&rdquo; has become the default explanation for everything wrong with the economy. It&rsquo;s also a convenient distraction that lets the real culprits off the hook.</p> <p>The story we tell ourselves about money printing is a relic of the gold standard, when central banks literally minted coins or printed paper redeemable for gold.</p> <h2 id="dual-mandate">Dual Mandate</h2> <p>Central banks are in fact very limited in their scope. Most are operating under a &ldquo;dual mandate&rdquo;, which consists of:</p> <ul> <li>Maximizing employment</li> <li>Stabilizing prices</li> </ul> <p>Needless to say, those two goals are often in conflict, but everything a central bank does is scoped by its mandate.</p> <h2 id="its-about-spending-covid-monetarism-vs-qe">It&rsquo;s About Spending: COVID Monetarism vs QE</h2> <p>Monetarists always warned that increasing money supply leads to inflation, and it&rsquo;s hard to dismiss them after what we saw during the COVID pandemic. Money supply surged in 2020, and by 2021 inflation followed. The correlation was clear, even if the causation was arguably more nuanced.</p> <p>QE didn&rsquo;t trigger inflation after 2008 because swapping bonds for reserves only fuels inflation if those reserves circulate, and they didn&rsquo;t. Banks hoarded them, velocity collapsed, and inflation never arrived.</p> <h2 id="what-actually-happens">What Actually Happens</h2> <p>Central banks don&rsquo;t fund deficits directly. The government issues bonds, dealers buy them, and the money gets spent.</p> <p>That said, the central bank is obviously the biggest player in the government bond market. By keeping demand high and yields low, the Fed enables larger deficits than the market would otherwise tolerate, stimulating aggregate demand and contributing to inflation, much like old‑fashioned money printing.</p> <p><figure> <a href="https://bubelov.com/blog/2026/do-central-banks-print-money/debt_hu_af847f542525b4c.webp"> <img src="https://bubelov.com/blog/2026/do-central-banks-print-money/debt_hu_a11da3bb7ab4a00a.webp" alt="" /> </a> </figure></p> <h2 id="what-if-the-fed-sold-all-its-bonds">What if the Fed Sold All its Bonds?</h2> <p>The immediate effect would be a massive supply shock in the bond market. With demand unable to absorb that much supply without a major price adjustment, bond prices would plummet and yields would spike. Mortgage rates, corporate borrowing costs, and government debt service would all jump virtually overnight.</p> <p>Of course, this is just a thought experiment since the Fed would never do this. Selling its entire portfolio would directly undermine its dual mandate.</p> <h2 id="fed-needed-qe-more-than-banks">Fed Needed QE More Than Banks</h2> <p>In 2008, banks weren&rsquo;t lobbying for the Fed to buy their bonds. The Fed came to them, not the other way around. With interest rates already at zero, conventional tools were exhausted. QE was the only lever left when doing nothing was politically unacceptable.</p> <p>Banks participated because the Fed offered to buy assets at favorable prices during a panic, but most large banks would have survived without it. The real beneficiary was the Fed itself, which needed QE to maintain the illusion of control when its traditional toolkit had run dry.</p> <h2 id="helicopter-money">Helicopter Money</h2> <p>Critics call QE &ldquo;printing money&rdquo; because it expands the monetary base. That&rsquo;s true for M0, but base money doesn&rsquo;t automatically translate into new demand or inflation.</p> <p>The central bank can flood the system with reserves, as it did after 2008. But broad money (M2), the measure that actually matters, is created primarily by private banks through lending.</p> <p><figure> <a href="https://bubelov.com/blog/2026/do-central-banks-print-money/m2_hu_12174803dfe211bc.webp"> <img src="https://bubelov.com/blog/2026/do-central-banks-print-money/m2_hu_fad9f569b5295855.webp" alt="" /> </a> </figure></p> <p>In 2020, the Fed once again deployed QE, but this time the Treasury acted alongside it, sending checks directly to households through stimulus payments, expanded unemployment benefits, and PPP loans.</p> <p>That money landed in checking accounts, and unlike banks, households spend. They buy groceries, pay rent, order from Amazon. Each transaction becomes someone else&rsquo;s income, ready to be spent again. That circulation is what finally pulled inflation out of its long hibernation.</p> <h2 id="who-really-prints-the-money">Who Really Prints the Money</h2> <p>The real problem is credit creation, and private banks do most of it.</p> <p>When banks make loans, they create money. Your mortgage didn&rsquo;t come from some pool of pre-existing savings. The bank created it, at the moment of lending, by typing numbers into your account. The money didn&rsquo;t exist before you borrowed it.</p> <p>The real &ldquo;printing press&rdquo; is the ledger system banks run. They&rsquo;re the ones creating money out of thin air every time they approve a loan. The central bank enables it, yes. But the banks are the ones pulling the trigger.</p> <h2 id="the-sovereignty-question">The Sovereignty Question</h2> <p>When the money supply expands, it&rsquo;s not just an economic act. It&rsquo;s a political one. It transfers wealth from savers to borrowers via inflation. It distorts price signals. It rewards the connected and punishes the prudent.</p> <p>The ability to create money is the <strong>ultimate form of power over others</strong>, and private banks are the true creators.</p> <h2 id="conclusion">Conclusion</h2> <p>The Fed buys bonds to manage interest rates, provide liquidity, and stabilize markets. Swapping bonds for reserves changes nothing about a bank&rsquo;s lending capacity.</p> <p>The &ldquo;printing&rdquo; narrative is a distraction. It lets us blame a single institution for a system where both central banks and private banks are complicit.</p> <p>Modern money is just a database. And like all databases, it&rsquo;s controlled by whoever holds the keys. The Fed holds some, but it&rsquo;s far from the only player.</p> The Beginning of Greek Philosophy (1/81) https://bubelov.com/blog/2026/philosophy-1/ Sat, 14 Feb 2026 00:00:00 +0000 https://bubelov.com/blog/2026/philosophy-1/ <p>This post is based on the notes I made watching identically named Artur Holmes lecture in his excellent History of Philosophy course.</p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#where-it-all-begins">Where it All Begins</a></li> <li><a href="#the-first-philosopher">The First Philosopher</a></li> <li><a href="#monism-vs-pluralism">Monism vs Pluralism</a></li> <li><a href="#duality">Duality</a></li> <li><a href="#ethics">Ethics</a></li> <li><a href="#the-annoying-guy">The Annoying Guy</a></li> <li><a href="#theology">Theology</a></li> <li><a href="#metaphysics">Metaphysics</a></li> <li><a href="#epistemology">Epistemology</a></li> <li><a href="#money">Money?</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="where-it-all-begins">Where it All Begins</h2> <p>Western philosophy begins with the Greeks, in the Aegean Sea, on the lips of thinkers in Miletus who were building on, but fundamentally breaking from, mythological traditions. The ruins of Miletus are still accessible for tourism, so it looks like a cool travel destination.</p> <p>Miletus was the wealthiest city in the Greek world, and that&rsquo;s the key to understanding the origins of philosophy. Wealth creates <strong>leisure</strong>, and wealth is also generated by <strong>trade</strong>, which involves <strong>cross-cultural exchange</strong>. Combine all that and you get a fertile ground for coming up with basic philosophical questions.</p> <p><figure> <a href="https://bubelov.com/blog/2026/philosophy-1/miletus_hu_b912f93cbce2f95f.webp"> <img src="https://bubelov.com/blog/2026/philosophy-1/miletus_hu_e280b65d97b4a2d4.webp" alt="" /> </a> <figcaption>Miletus</figcaption> </figure></p> <h2 id="the-first-philosopher">The First Philosopher</h2> <p>Thales of Miletus (626–548 BC) is considered the first known philosopher of the western tradition. He was a pre-Socratic Greek thinker who proposed that the fundamental principle (or arche) of all things is water. He is also remembered as one of the legendary &ldquo;Seven Wise Men&rdquo; of Greece for his practical wisdom, which reportedly included using his knowledge of astronomy to predict a bountiful olive harvest.</p> <p>Here, we&rsquo;re getting into <strong>Monism</strong>, the intuitive idea that everything boils down to a single fundamental building block.</p> <h2 id="monism-vs-pluralism">Monism vs Pluralism</h2> <p>The elements battle boiled down to Monism vs <strong>Pluralism</strong>: how many elements are really basic? There was an initial tendency to look for a single basic element. Thales thought that everything is derived from water: liquid, solid and vapor. It is essential to life. Water is really fundamental to everything. Maybe it&rsquo;s the basic stuff?</p> <p>The early philosophers played with different elements: earth, air, fire and water. Those are all popular Greek elements.</p> <p>Philosophers can be seen as pre-scientific scientists. Early philosophers were obsessed with basic elements, primitive cosmology, and most importantly, <strong>morality</strong>. Do we have a cosmic justice? Not that different from the Chinese Mandate of Heaven, is it?</p> <h2 id="duality">Duality</h2> <p>In the late 6th century BCE, two philosophers, Pythagoras and Heraclitus, independently recognized that reality is defined by <strong>duality</strong> and <strong>change</strong>.</p> <p>Pythagoras observed that everything has two opposing aspects: limited and unlimited, odd and even, male and female. He believed these opposites were held together by harmony, which could be expressed through numbers. For him, the universe was an <strong>ordered cosmos</strong> where opposites coexist in balance.</p> <p>Heraclitus, on the other hand, was struck by the fact that everything is constantly changing. He used fire as his metaphor: like fire, the world is always consuming and transforming. He also saw that opposites are locked in <strong>perpetual tension</strong>: life and death, waking and sleeping, but argued that this very conflict creates unity. As he put it, &ldquo;Harmony consists of opposing tension, like that of the bow and the lyre.&rdquo;</p> <p>In other words: both saw two sides to everything. Pythagoras sought the eternal harmony between them, while Heraclitus embraced the perpetual fire of their conflict.</p> <h2 id="ethics">Ethics</h2> <p>A cosmic order also implies the need for an ordered life, and with that realization, <strong>ethics</strong> enters the stage. The intuitive idea is that if the universe has a rational structure, then human beings, as part of that universe, should align themselves with it.</p> <h2 id="the-annoying-guy">The Annoying Guy</h2> <p>Zeno was a remarkable figure and he was busy with paradoxes. Change is a paradoxical self-contradictory thing. Hare and tortoise mental experiments, etc. That Zeno guy, he pissed off everyone and no one wanted to follow him. If everything is illusory, why even say that? It&rsquo;s self-defeating, but he forced later philosophers to try refuting his arguments.</p> <h2 id="theology">Theology</h2> <ul> <li>Anaxagoras (The Teleologist: Purpose/God/Mind)</li> <li>Democritus (The Mechanist: Blind Forces/Chance/Matter)</li> </ul> <p>They created western agenda. What are the basic stuff. We still ask. Protons, quarks, your choice.</p> <p>Every major debate in western philosophy and science can be traced back to the clash between these two fifth century BCE thinkers. They asked the same question, &ldquo;What is the fundamental nature of reality?&rdquo;, and arrived at answers that are not only opposite but irreconcilable.</p> <h2 id="metaphysics">Metaphysics</h2> <p>The Pre-Socratics were the first western thinkers to ask purely metaphysical questions:</p> <ul> <li><strong>What actually exists?</strong> Only the physical world? Or something beyond it? (Pythagoras pointed to numbers, Parmenides pointed to an unchanging One behind appearances)</li> <li><strong>What does it mean for something to be?</strong> (Parmenides drew the first stark line between Being and non-Being)</li> <li><strong>Is reality one thing or many things?</strong> (The Monists said one, the Pluralists said many)</li> </ul> <h2 id="epistemology">Epistemology</h2> <p>While epistemology becomes the central obsession of the Modern period (Descartes, Hume, Kant), its roots are firmly planted in Pre-Socratic soil. When Parmenides used logic to declare that change is impossible, contradicting his own senses, he forced philosophy to ask: which source of knowledge do we trust? The answer to &ldquo;What is real?&rdquo; depends entirely on &ldquo;How do we know?&rdquo;</p> <h2 id="money">Money?</h2> <p>Miletus was also one of the first places to adopt standardized coinage, and it had its own mint and coin design. The chemical composition and the technology itself was borrowed from the neighboring Lydia. It&rsquo;s unclear whether there is a link between the birth of abstract money and the birth of abstract thought, but that might not be a pure coincidence.</p> <p><figure> <a href="https://bubelov.com/blog/2026/philosophy-1/coin_hu_ee126827534805b9.webp"> <img src="https://bubelov.com/blog/2026/philosophy-1/coin_hu_4e6c924b93d4e427.webp" alt="" /> </a> <figcaption>Miletus coin</figcaption> </figure></p> <h2 id="conclusion">Conclusion</h2> <p>The introduction to the history of philosophy is pretty dense, and it makes it clear that philosophy doesn&rsquo;t really advance as quickly as other sciences, if it even advances at all. I mean, all those fundamental questions the early Greeks discovered are still unresolved. It really helps you appreciate the &ldquo;eternal&rdquo; stuff, philosophy doesn&rsquo;t focus on the ephemeral.</p> Personal Sovereignty Starts with National Sovereignty https://bubelov.com/blog/2026/sovereignty/ Fri, 13 Feb 2026 00:00:00 +0000 https://bubelov.com/blog/2026/sovereignty/ <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#preface">Preface</a></li> <li><a href="#the-limits-of-selfhosting">The Limits of Self‑Hosting</a></li> <li><a href="#the-tsmc-problem">The TSMC Problem</a></li> <li><a href="#the-scale-problem">The Scale Problem</a></li> <li><a href="#the-better-model">The Better Model</a></li> <li><a href="#bitcoins-quiet-stake-in-sovereignty">Bitcoin&rsquo;s Quiet Stake in Sovereignty</a></li> <li><a href="#sovereignty-is-layered">Sovereignty Is Layered</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="preface">Preface</h2> <p>There&rsquo;s a seductive narrative in tech circles, one that can be boiled down to this: <strong>individual sovereignty is simply a matter of better tools</strong>. Self‑host your data, run a Bitcoin node, encrypt your communications, and you&rsquo;ve liberated yourself from the system.</p> <p>It&rsquo;s a compelling vision, but it&rsquo;s <strong>incomplete</strong>.</p> <p>Essentially, this is <strong>digital primitivism</strong>. Like its agrarian analogue, it trades efficiency for autonomy, opts out of centralized systems, and dreams of a world where small, self‑sufficient units replace vast, impersonal networks. I say this with sympathy, I am one of its practitioners.</p> <p>The uncomfortable truth is that personal sovereignty rests on a foundation most of us prefer to ignore: <strong>national sovereignty</strong>. You cannot opt out of a world shaped by borders, supply chains, and concentrated industrial power.</p> <h2 id="the-limits-of-selfhosting">The Limits of Self‑Hosting</h2> <p>I run my own Bitcoin nodes. I host my own files, sync my own contacts, and route my own traffic. I&rsquo;ve cut Google and Apple out of my life to the greatest extent possible. This feels like freedom, and in many ways, it is.</p> <p>But this stuff runs on a computer built in China, designed in America, powered by Taiwanese semiconductors, and shipped across oceans on tankers insured by Lloyd&rsquo;s of London.</p> <p>If the Taiwan Strait becomes contested, I won&rsquo;t be getting hardware upgrades. If export controls tighten, I&rsquo;m in trouble. If a state actor decides my traffic is suspicious, it already has all the tools to block it.</p> <p>Self‑hosting gives me control over my software. It does nothing to secure the physical, industrial, and geopolitical layers beneath it.</p> <p>This is the same vulnerability Jefferson&rsquo;s free farmers faced. They could work their own land, govern their own towns, and print their own pamphlets, but they could not build their own cannons, forge rifles at scale, or defend a coastline without a navy.</p> <p>Autonomy at the local level does not eliminate dependency. The real choice is not between dependency and independence, but between <strong>national</strong> dependency (resilience‑optimized) and <strong>transnational</strong> dependency (efficiency‑optimized).</p> <h2 id="the-tsmc-problem">The TSMC Problem</h2> <p>Consider the most foundational technology of our era: advanced semiconductors. There is effectively one company, TSMC, that is the only reliable high‑volume manufacturer of the most advanced chips. This isn&rsquo;t really a market failure, it&rsquo;s more of a physics and capital failure. A single leading‑edge fab now costs tens of billions of dollars and requires a decade of specialized expertise to operate.</p> <p>This is a centralization point of historic proportions. It makes every nation that depends on advanced chips, which is to say <strong>every nation</strong>, profoundly vulnerable. The global economy&rsquo;s most critical layer is controlled by a single company, headquartered on an island of immense geopolitical tension.</p> <p>Bitcoin&rsquo;s security model <strong>assumes distributed hash power</strong>. If TSMC were to cease shipping ASICs, replenishing the global hash rate would become catastrophically difficult. A clever consensus mechanism is more or less useless if we can&rsquo;t fix the fragility of the hardware supply chain.</p> <p>This is not a problem you can self‑host your way out of.</p> <h2 id="the-scale-problem">The Scale Problem</h2> <p>There is a pattern here. Some things can be done by individuals: running software, storing keys, verifying transactions. Some things can be done by communities: operating mesh networks, pooling hash power, maintaining public repositories.</p> <p>This is the digital analogue of Jefferson&rsquo;s agrarian republic: a world of self‑sufficient homesteads, each running its own infrastructure, trading surpluses with neighbors, and asking little from the center. It is beautiful, it is desirable, and it is defenseless against a power that operates at scale.</p> <p>The capital and expertise required to operate a leading‑edge fab are so immense that only <strong>nation‑states</strong> have both the incentive and the resources to make it happen.</p> <p>We can call the current state of things <strong>transnational centralization</strong> (one company, one jurisdiction). The desired, and, in my view, perfectly achievable, better state is <strong>national decentralization</strong> (multiple nations, multiple independent supply chains).</p> <h2 id="the-better-model">The Better Model</h2> <p>A more resilient world would have national semiconductor fabs, not because markets prefer it, but because nations <strong>insist</strong> on it. The logic is no different from food security: we prop up local producers and impose protectionist tariffs not for efficiency, but for survival. The same reasoning applies to sovereign satellite navigation systems, redundant power grids, and every other piece of critical infrastructure we silently depend on.</p> <p>Is this plain old autarky? Well, yeah. I would even say it&rsquo;s just a compromise between the ideal of anarcho-primitivism and reality. You can&rsquo;t get rid of concentrated power, and current national identity-based communities are a sweet spot.</p> <p>Why cling to national identity? Because it&rsquo;s the only way to protect a reasonably decentralized system from invasion by a larger, better‑organized foreign power.</p> <p>Take Jefferson&rsquo;s vision of a &ldquo;society of free farmers.&rdquo; How do you defend it without a shared identity, a common language, cohesive traditions, and an economy of scale large enough to repel foreign aggression?</p> <p>You don&rsquo;t.</p> <p>This is also why I find the libertarian hostility to industrial policy so misguided. Markets optimize for efficiency, they do not optimize for resilience. A perfectly efficient global supply chain is one where every component is produced by the single lowest‑cost supplier. That is exactly what we built, and exactly why the current system is so catastrophically fragile.</p> <h2 id="bitcoins-quiet-stake-in-sovereignty">Bitcoin&rsquo;s Quiet Stake in Sovereignty</h2> <p>Bitcoin maximalists often frame their project as a complete alternative to the nation‑state. But Bitcoin itself is deeply dependent on nation‑states.</p> <p>It depends on Taiwanese semiconductors. It depends on Chinese manufacturing of mining hardware. It depends on undersea cables controlled by western intelligence alliances. It depends on a stable global energy market. It depends on the rule of law, and the sanity of lawmakers, in the jurisdictions where exchanges, developers, and node operators reside.</p> <p>Bitcoin cannot secede from geography. It can only hope to be tolerated by enough nations that no single one can destroy it. <strong>This is a bet on a multipolar world order</strong>.</p> <p>But multipolarity alone isn&rsquo;t enough. You also need to bet on <strong>perpetual great‑power rivalry</strong>. If major nations ever reach broad ideological alignment, Bitcoin is finished. They would simply agree to coordinate a global, all‑out attack, and there would be nowhere left to run.</p> <h2 id="sovereignty-is-layered">Sovereignty Is Layered</h2> <p>Running your own node and supporting your nation&rsquo;s push for sovereignty aren&rsquo;t opposing impulses. They&rsquo;re <strong>complementary</strong> activities.</p> <p>Prefer local. Avoid global. And when you can&rsquo;t avoid it, make sure the global isn&rsquo;t controlled by a single point of failure.</p> <p>The personal layer gives you control over your keys, your data, your immediate digital life. The national layer ensures the industrial base that produces your hardware remains plural and resilient. The personal layer is about agency. The national layer is about survival.</p> <p>Both are critical. Neither can substitute for the other.</p> <p>This is the synthesis I&rsquo;ve arrived at after years of pursuing individual sovereignty while watching the world&rsquo;s most critical supply chains concentrate into single points of failure. You cannot self‑host a fab. You cannot mesh‑network a steel mill. You cannot fork the global supply chain.</p> <p>But you can demand that your nation build redundant capacity. You can advocate for industrial policy that <strong>prioritizes resilience over efficiency</strong>. You can recognize that the same centralization risks we fight in software are even more dangerous in hardware, and that the nation‑state is the only institution with the scale and legitimacy to address them.</p> <p>Personal sovereignty starts with national sovereignty. Not ends there, starts there. The rest is up to us.</p> <h2 id="conclusion">Conclusion</h2> <p>In this post, I&rsquo;ve tried to identify the central flaw in the anarcho‑primitivist logic I largely sympathize with, and to suggest a path forward from the horrors of the <a href="https://bubelov.com/blog/2020/industrial-society-and-its-future/">&ldquo;Industrial Society&rdquo;</a>, as warned by both Ted Kaczynski and, in a far more temperate key, by critics of industrial centralization from Jefferson to Wendell Berry.</p> <p>I think of digital primitivism as a kind of guiding star. It shows us what full sovereignty could look like. But it also shows us what full sovereignty cannot defend. The homesteader needs a navy. The self‑hoster needs a fab. The commune needs a grid. These things do not emerge from voluntary association, they require the coercive, extractive, legitimized power of the nation‑state.</p> <p>The task, then, is not to abolish that power. It is to pluralize it.</p> <p>People are ready to trade some efficiency for more sovereignty and resilience. But expecting the majority to embrace radical, individual‑level decentralization is a fantasy. So let&rsquo;s start with something achievable: proper <strong>national‑level decentralization</strong>.</p> <p>Set the nation‑state against transnational capital. Build redundant supply chains. Insist on national sovereignty. And see what happens.</p> Core Lightning Post Mortem https://bubelov.com/blog/2026/cln/ Thu, 12 Feb 2026 00:00:00 +0000 https://bubelov.com/blog/2026/cln/ <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#the-old-setup">The Old Setup</a></li> <li><a href="#the-cln-shenanigans">The CLN Shenanigans</a></li> <li><a href="#the-dynamic-linking-problem">The Dynamic Linking Problem</a></li> <li><a href="#lnd-gets-it-right">LND Gets It Right</a></li> <li><a href="#the-counterarguments">The Counterarguments</a></li> <li><a href="#what-i-miss">What I Miss</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="the-old-setup">The Old Setup</h2> <p>I set up BTC Map&rsquo;s first Lightning node in late 2022, and I picked CLN, mostly because it was ahead of the curve on BOLT‑12 adoption. Installing <code>lightningd</code> wasn&rsquo;t easy, though. It only officially supports Ubuntu, and BTC Map&rsquo;s fleet runs on Arch.</p> <p>So let&rsquo;s take a closer look at the CLN package.</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">tar --extract --file clightning-v24.08.1-Ubuntu-24.04.tar.xz --verbose </span></span></code></pre></div><h2 id="the-cln-shenanigans">The CLN Shenanigans</h2> <p>What follows is a complete explosion of files. Dozens of binaries, plugins, Python scripts, documentation, and even <code>.github</code> folders with screenshots, shipped accidentally inside the package.</p> <p>I&rsquo;ve trimmed the output, but the picture is clear:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">./ </span></span><span class="line"><span class="cl">./usr/ </span></span><span class="line"><span class="cl">./usr/bin/ </span></span><span class="line"><span class="cl">./usr/bin/lightning-cli </span></span><span class="line"><span class="cl">./usr/bin/lightning-hsmtool </span></span><span class="line"><span class="cl">./usr/bin/lightningd </span></span><span class="line"><span class="cl">./usr/bin/reckless </span></span><span class="line"><span class="cl">./usr/libexec/ </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/ </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/lightning_channeld </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/lightning_closingd </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/lightning_connectd </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/lightning_dualopend </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/lightning_gossipd </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/lightning_hsmd </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/lightning_onchaind </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/lightning_openingd </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/lightning_websocketd </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/ </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/autoclean </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/bcli </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/bookkeeper </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/chanbackup </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/cln-askrene </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/cln-grpc </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/cln-renepay </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/clnrest/ </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/clnrest/.github/ </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/clnrest/.github/screenshots/ </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/clnrest/.github/screenshots/Postman-bkpr-plugin.png </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/clnrest/.github/screenshots/Postman-with-body.png </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/clnrest/.github/screenshots/Postman.png </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/clnrest/.github/screenshots/Swagger-auth.png </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/clnrest/.github/screenshots/Swagger-list-methods.png </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/clnrest/.github/screenshots/Swagger-rpc-method.png </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/clnrest/.github/screenshots/Swagger.png </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/clnrest/Makefile </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/clnrest/__init__.py </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/clnrest/clnrest </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/clnrest/clnrest.py </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/clnrest/poetry.lock </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/clnrest/pyproject.toml </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/clnrest/utilities/ </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/clnrest/utilities/__init__.py </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/clnrest/utilities/generate_certs.py </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/clnrest/utilities/rpc_plugin.py </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/clnrest/utilities/rpc_routes.py </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/clnrest/utilities/shared.py </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/commando </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/funder </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/keysend </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/offers </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/pay </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/recklessrpc </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/recover </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/spenderp </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/sql </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/topology </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/txprepare </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/wss-proxy/ </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/wss-proxy/Makefile </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/wss-proxy/poetry.lock </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/wss-proxy/pyproject.toml </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/wss-proxy/wss-proxy </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/wss-proxy/wss-proxy.py </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/wss-proxy/wss_proxy/ </span></span><span class="line"><span class="cl">./usr/libexec/c-lightning/plugins/wss-proxy/wss_proxy/__init__.py </span></span><span class="line"><span class="cl">./usr/share/ </span></span><span class="line"><span class="cl">./usr/share/doc/ </span></span><span class="line"><span class="cl">./usr/share/doc/c-lightning/ </span></span><span class="line"><span class="cl">./usr/share/doc/c-lightning/LICENSE </span></span><span class="line"><span class="cl">./usr/share/doc/c-lightning/README.md </span></span><span class="line"><span class="cl">./usr/share/man/... <span class="o">(</span>OMITTED<span class="o">)</span> </span></span></code></pre></div><p>This is complete insanity. Usually, you just need a single binary, like <code>bitcoind</code>, and you&rsquo;re done. But CLN takes an unorthodox approach: instead of one self-contained executable, you get dozens of moving parts glued together in weird ways.</p> <p>Core Lightning is a maintenance nightmare.</p> <h2 id="the-dynamic-linking-problem">The Dynamic Linking Problem</h2> <p>It gets worse. For reference, Bitcoin Core binaries work on virtually any Linux system because they&rsquo;re statically linked. I expected the same from CLN, but no. CLN relies heavily on dynamic linking, which means it makes bold assumptions about the host environment.</p> <p>My last attempt at updating CLN failed because the system had a newer version of <code>libsodium</code> or something like that, it adds completely unnecessary friction and a constant source of uncertainty.</p> <p>Combine that with the convoluted packaging, and you get the least sane Lightning implementation I&rsquo;ve ever dealt with.</p> <h2 id="lnd-gets-it-right">LND Gets It Right</h2> <p>LND, on the other hand, is almost perfect. It follows the <code>bitcoind</code> playbook to the letter:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">tar --extract --file lnd-linux-amd64-v0.20.0-beta.tar.gz --verbose </span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">lnd-linux-amd64-v0.20.0-beta/lnd </span></span><span class="line"><span class="cl">lnd-linux-amd64-v0.20.0-beta/lncli </span></span></code></pre></div><p>That&rsquo;s it. Two binaries. No hacky plugins, no screenshots, no sprawling directory trees, no Python dependencies, no <code>.github</code> folders.</p> <p>Check the dependencies:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ldd lnd </span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">not a dynamic executable </span></span></code></pre></div><p>Statically linked. Self-contained. Upgrade is a simple file swap. This is the only deployment pattern that makes sense for a decentralized network with a huge number of self-hosted nodes.</p> <h2 id="the-counterarguments">The Counterarguments</h2> <p>Of course, there are always Ubuntu maxis and the Docker crowd, ready to sell you their &ldquo;solutions&rdquo; while conveniently ignoring the baggage such as invasive abstraction layers and all the extra complexity.</p> <h2 id="what-i-miss">What I Miss</h2> <p>The one thing LND still lacks is full BOLT‑12 support, and that&rsquo;s the only reason I originally chose CLN. In practice, though, BOLT‑11 has stayed relevant far longer than I expected and it will keep being a reasonable default option for the years to come. BOLT‑12 development is progressing slowly but steadily, and we&rsquo;ll get there eventually.</p> <h2 id="conclusion">Conclusion</h2> <p>If you&rsquo;re running a Lightning node and value your sanity, pick LND. It&rsquo;s the only implementation that aims to be usable.</p> Wireless Charging https://bubelov.com/blog/2026/wireless/ Sun, 08 Feb 2026 00:00:00 +0000 https://bubelov.com/blog/2026/wireless/ <h2 id="the-accident">The Accident</h2> <p>It all started with a deep clean. Buried in a tangle of old cables, I found a dusty wireless charger I&rsquo;d completely forgotten about, a &ldquo;Mi Wireless Charger&rdquo;. I have no idea where it came from.</p> <p>On a whim, I dropped my Google Pixel 6 onto it. To my surprise, the screen lit up: &ldquo;<strong>Charging Wirelessly</strong>&rdquo;. My power meter showed a solid 4W flowing into the phone, occasionally bumping up to 5W. Not exactly fast-charging, but for a forgotten gadget, it was above my expectations.</p> <p>That got me curious. This old thing was using the Qi standard, something I&rsquo;d known about for years but never really paid attention to. Had it gotten any better over time? And were there newer, faster options out there now?</p> <h2 id="the-wpc">The WPC</h2> <p>A quick dive into the standard revealed the orchestrator: the <strong>Wireless Power Consortium (WPC)</strong>. This isn&rsquo;t a shadowy cabal, but an open alliance of hundreds of companies, from Apple to Xiaomi, that agreed on one crucial thing: ending the cord war.</p> <p>Before Qi, wireless charging was a mess of incompatible pads and proprietary tech. The WPC&rsquo;s mission was to create a single, global standard. Their success is the only reason that old, forgotten pad could still charge my relatively modern phone. It&rsquo;s a rare and beautiful case of industry cooperation that actually benefited users.</p> <p>The WPC hasn&rsquo;t been idle, though. The standard evolved, and today <strong>Qi2</strong>, the next generation with magnetic alignment, is finally taking off.</p> <h2 id="magnets">Magnets</h2> <p>The big improvement in Qi2 isn&rsquo;t just raw speed, it&rsquo;s alignment. And now I understand why my old pad was so flaky, bouncing between 4W and 5W.</p> <p>The new <strong>Magnetic Power Profile (MPP)</strong> borrows the best idea from Apple&rsquo;s MagSafe: built‑in ring magnets. Suddenly, all the side effects of misalignment go away: the slight buzzing, the flickering light, the heat buildup as the charger tries and fails to lock on.</p> <p>Magnets solve that. Perfect alignment means more efficient power transfer, so charging is not only faster, but also noticeably cooler.</p> <p>A nice bonus: now that alignment is guaranteed, manufacturers are getting creative with angles. You can find magnetic chargers that hold your phone at 30° or 45°, turning your phone into a handy video screen while it powers up.</p> <h2 id="the-bridge-to-the-old-world">The Bridge to the Old World</h2> <p>The problem is, my Pixel 6 isn&rsquo;t getting a Qi2 update cuz it&rsquo;s a hardware limitation, not a software patch. The only way to upgrade is to buy a new phone, like a Pixel 10, and I&rsquo;m not ready for that. I plan to keep using my Pixel 6 for another year, or at least until the battery gives out.</p> <p>Even so, I think getting a Qi2 charger now is still a smart move. You can buy a phone case with a strong magnetic ring built in, which gives you most of the alignment benefits of Qi2 without needing a new phone. It&rsquo;s a perfect bridge between the old standard and the new one.</p> <h2 id="conclusion">Conclusion</h2> <p>Curiosity won. I ordered a Qi2 charger from Ugreen.</p> <p>Qi was a great foundation, and Qi2 builds on it by finally fixing the frustrating user experience.</p> <p>This all started with a dusty old Qi pad I&rsquo;d completely forgotten about. That accidental find sent me down a rabbit hole that ended with a much better charging setup. It was a good reminder that sometimes the best tech upgrades aren&rsquo;t about raw speed, but about a simple, satisfying snap that just makes everything work better.</p> Blog Cleanup https://bubelov.com/blog/2026/blog-cleanup/ Sat, 07 Feb 2026 00:00:00 +0000 https://bubelov.com/blog/2026/blog-cleanup/ <p>My old blog post structure looked like that: <code>/year/month/title.md</code>. With a few hundred posts, I needed folders, but splitting by month was overkill. Most months only had one post, leaving me with a sea of near-empty directories that made finding and referencing old content a chore.</p> <p>I&rsquo;ve switched to a simpler yearly split (<code>/year/title.md</code>). A flat list per year is far easier to navigate. It works perfectly if you average less than five posts a month, no folder ever grows beyond ~100 items, and it can handle the occasional burst without breaking a sweat.</p> <p>This change will break some old links. I&rsquo;m not setting up redirects in some complicated edge cases, since it&rsquo;s not worth the hassle.</p> <p>I also did some serious housecleaning. I deleted short, trivial posts and split a few overly long ones. I&rsquo;ve added two new tags: <code>Pinned</code> for posts worth highlighting, and <code>Flagged</code> for those needing a rewrite or update.</p> <p>Most cuts were easy. Many old posts were ephemeral &ldquo;how to set up X on Y&rdquo; tutorials where both X and Y are now obsolete. I&rsquo;m sunsetting that entire format. There are exceptions like guides for stable, durable projects like <code>bitcoind</code>. They&rsquo;re useful for historical comparison in my future posts.</p> <p>My new rule of thumb is simple: Will this post matter to anyone (and especially myself) three years from now? If the answer is &ldquo;no&rdquo;, I won&rsquo;t write it. If it was already live and failed the test, it&rsquo;s now gone.</p> <p>The goal is a leaner, more durable blog, one where everything that remains has a reason to stick around. The changes probably affected about 20% of all posts, and it can cause some temporary issues with webfeed readers.</p> Smartphone Lifespan and Pricing https://bubelov.com/blog/2026/smartphone-lifespan-pricing/ Fri, 06 Feb 2026 00:00:00 +0000 https://bubelov.com/blog/2026/smartphone-lifespan-pricing/ <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#preface">Preface</a></li> <li><a href="#samsung-galaxy-gio-2011-2012">Samsung Galaxy Gio (2011-2012)</a></li> <li><a href="#nexus-4-2012-2013">Nexus 4 (2012-2013)</a></li> <li><a href="#nexus-5-2013-2014">Nexus 5 (2013-2014)</a></li> <li><a href="#sony-xperia-z2-2014-2015">Sony Xperia Z2 (2014-2015)</a></li> <li><a href="#nexus-5x-and-6p-2015-2017">Nexus 5X and 6P (2015-2017)</a></li> <li><a href="#xiaomi-mi-a1-2017-2020">Xiaomi Mi A1 (2017-2020)</a></li> <li><a href="#samsung-a01-2020-2022">Samsung A01 (2020-2022)</a></li> <li><a href="#google-pixel-6-2022-2026">Google Pixel 6 (2022-2026)</a></li> <li><a href="#next-grapheneos-phone">Next: GrapheneOS Phone?</a></li> <li><a href="#is-it-just-me">Is it Just Me?</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="preface">Preface</h2> <p>As a maintainer of a few Android apps, I&rsquo;ve been into smartphones for a long time. I always thought I should upgrade every year or two to get all the cool new software and hardware features. But it looks like those old rules don&rsquo;t really apply anymore.</p> <p>Here&rsquo;s the list of smartphones I&rsquo;ve owned and what I thought about using them.</p> <h2 id="samsung-galaxy-gio-2011-2012">Samsung Galaxy Gio (2011-2012)</h2> <p>My first real &ldquo;smart&rdquo; phone was a Samsung Galaxy Gio. I bought it for about $130, and it was a terrible phone. I couldn&rsquo;t even set an alarm since the alarm manager app took forever to open. There was also a special button you could click to clear your RAM. It seemed to help sometimes, so a lot of us Android 2.2 users would tap that button, hoping for a speed boost.</p> <h2 id="nexus-4-2012-2013">Nexus 4 (2012-2013)</h2> <p>My next smartphone was the Nexus 4, and it was probably the best smartphone I&rsquo;ve ever owned. It had a great design and outstanding performance. The price was a bit higher, around $280, but it was worth every penny. In fact, I knew a few people who were still using this phone in 2018.</p> <h2 id="nexus-5-2013-2014">Nexus 5 (2013-2014)</h2> <p>I was sure I&rsquo;d buy the next Nexus as soon as it came out, and that&rsquo;s exactly what I did. It was my first disappointment with the line. Don&rsquo;t get me wrong, the phone was fine performance-wise. But the design wasn&rsquo;t great, and it felt cheap, which was surprising given its $300 price.</p> <p>This phone started failing after about a year of heavy use. I think it was battery‑related: it always gave wrong charge readings and would shut down while still showing 40% battery.</p> <h2 id="sony-xperia-z2-2014-2015">Sony Xperia Z2 (2014-2015)</h2> <p>After my Nexus 5 died, I decided to try Sony and bought an Xperia Z2. It cost about $300, the same as the Nexus 5, and I really liked it. The phone was powerful, looked good, and could last up to three days on a single charge, way longer than my Nexus 5 ever managed, even before its battery started to fail.</p> <p>This phone actually fell off a boat near the Phi Phi islands in Thailand. I found it, and it kept working for a few more months after that.</p> <h2 id="nexus-5x-and-6p-2015-2017">Nexus 5X and 6P (2015-2017)</h2> <p>I had fond memories of the Nexus 4, and I thought Google had learned from the Nexus 5&rsquo;s missteps, that the new models would be more like the Nexus 4. So I bought both the Nexus 5X and the 6P, despite their higher price tags ($400 and $600).</p> <p>Both phones worked well for the first year. Then the problems started. The Nexus 5X basically melted, Google admitted it was a widespread manufacturing issue. The phones were poorly assembled, and some internal components would lose contact, causing them to fail. My Nexus 6P lasted a bit longer, but then the battery started giving out, it could lose a full charge in an hour or two.</p> <h2 id="xiaomi-mi-a1-2017-2020">Xiaomi Mi A1 (2017-2020)</h2> <p>I tried a Xiaomi phone back in 2014 and it was terrible, so I&rsquo;d written off the brand. Then I saw one of Xiaomi&rsquo;s new phones listed on the Android One project site and liked what I saw. The deal was that Google would handle the UI and software updates, the thing that made the Nexus line so great, and Xiaomi would take care of manufacturing. I liked the arrangement, so I ordered a Mi A1 for about $200. At the time, that seemed ridiculously cheap next to the new Pixel phones, which started at $1,000.</p> <p>This phone wasn&rsquo;t perfect, but it was solid by any measure. Performance was great, no noticeable slowdowns in any app I used, and the battery life was pretty good, too.</p> <h2 id="samsung-a01-2020-2022">Samsung A01 (2020-2022)</h2> <p>Failing hardware isn&rsquo;t the only thing that forces a smartphone change. Having up‑to‑date firmware with the latest security patches matters, and my old Xiaomi Mi A1 started falling dangerously behind, eventually. It was the first time I upgraded because of software end‑of‑life, not because the hardware gave out.</p> <p>I decided to come full circle and give Samsung another shot. It was a double risk, my first Samsung was awful, and this time I went for the absolute cheapest model in their lineup: the Samsung A01, which was selling for about $94 in Thailand.</p> <p>Surprisingly, I have to admit this phone was great. I enjoyed it, and it lasted a couple of years. For this price, I wouldn’t even be mad if it died right after that two‑year mark.</p> <p>The only real issue, and it’s true for most Samsung phones, is all the bloatware. If you&rsquo;re into Android, though, that&rsquo;s a non‑issue. You can strip it all out with ADB. Here&rsquo;s the script I used:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash </span></span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="nb">declare</span> -a <span class="nv">bastards</span><span class="o">=(</span> </span></span><span class="line"><span class="cl"><span class="s2">&#34;com.facebook.services&#34;</span> </span></span><span class="line"><span class="cl"><span class="s2">&#34;com.facebook.katana&#34;</span> </span></span><span class="line"><span class="cl"><span class="s2">&#34;com.facebook.system&#34;</span> </span></span><span class="line"><span class="cl"><span class="s2">&#34;com.facebook.appmanager&#34;</span> </span></span><span class="line"><span class="cl"><span class="s2">&#34;com.lazada.android&#34;</span> </span></span><span class="line"><span class="cl"><span class="s2">&#34;com.linkedin.android&#34;</span> </span></span><span class="line"><span class="cl"><span class="s2">&#34;com.microsoft.skydrive&#34;</span> </span></span><span class="line"><span class="cl"><span class="s2">&#34;com.microsoft.office.officehubrow&#34;</span> </span></span><span class="line"><span class="cl"><span class="s2">&#34;com.spotify.music&#34;</span> </span></span><span class="line"><span class="cl"><span class="s2">&#34;com.sec.android.app.sbrowser&#34;</span> </span></span><span class="line"><span class="cl"><span class="s2">&#34;com.google.android.apps.youtube.music&#34;</span> </span></span><span class="line"><span class="cl"><span class="s2">&#34;com.sec.android.app.popupcalculator&#34;</span> </span></span><span class="line"><span class="cl"><span class="s2">&#34;com.sec.android.app.myfiles&#34;</span> </span></span><span class="line"><span class="cl"><span class="s2">&#34;com.google.android.apps.photos&#34;</span> </span></span><span class="line"><span class="cl"><span class="s2">&#34;com.sec.android.app.fm&#34;</span> </span></span><span class="line"><span class="cl"><span class="s2">&#34;com.aura.oobe.samsung.gl&#34;</span> </span></span><span class="line"><span class="cl"><span class="s2">&#34;com.google.android.videos&#34;</span> </span></span><span class="line"><span class="cl"><span class="s2">&#34;com.sec.android.gallery3d&#34;</span> </span></span><span class="line"><span class="cl"><span class="s2">&#34;com.sec.android.app.samsungapps&#34;</span> </span></span><span class="line"><span class="cl"><span class="s2">&#34;com.google.android.youtube&#34;</span> </span></span><span class="line"><span class="cl"><span class="s2">&#34;com.google.android.apps.docs&#34;</span> </span></span><span class="line"><span class="cl"><span class="s2">&#34;com.google.android.apps.tachyon&#34;</span> </span></span><span class="line"><span class="cl"><span class="s2">&#34;com.sec.android.app.camera&#34;</span> </span></span><span class="line"><span class="cl"><span class="s2">&#34;com.samsung.android.messaging&#34;</span> </span></span><span class="line"><span class="cl"><span class="s2">&#34;com.samsung.android.dialer&#34;</span> </span></span><span class="line"><span class="cl"><span class="s2">&#34;com.samsung.android.calendar&#34;</span> </span></span><span class="line"><span class="cl"><span class="s2">&#34;com.google.android.gm&#34;</span> </span></span><span class="line"><span class="cl"><span class="s2">&#34;com.sec.android.app.clockpackage&#34;</span> </span></span><span class="line"><span class="cl"><span class="s2">&#34;com.samsung.android.app.contacts&#34;</span> </span></span><span class="line"><span class="cl"><span class="o">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl">adb shell <span class="s1">&#39;pm list packages&#39;</span> &gt; packages.txt </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">for</span> bastard in <span class="s2">&#34;</span><span class="si">${</span><span class="nv">bastards</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">;</span> <span class="k">do</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> grep -q <span class="s2">&#34;</span><span class="nv">$bastard</span><span class="s2">&#34;</span> packages.txt </span></span><span class="line"><span class="cl"> <span class="k">then</span> </span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;Found </span><span class="nv">$bastard</span><span class="s2">&#34;</span> </span></span><span class="line"><span class="cl"> adb shell pm uninstall --user <span class="m">0</span> <span class="s2">&#34;</span><span class="nv">$bastard</span><span class="s2">&#34;</span> </span></span><span class="line"><span class="cl"> <span class="k">else</span> </span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$bastard</span><span class="s2"> not found&#34;</span> </span></span><span class="line"><span class="cl"> <span class="k">fi</span> </span></span><span class="line"><span class="cl"><span class="k">done</span> </span></span></code></pre></div><h2 id="google-pixel-6-2022-2026">Google Pixel 6 (2022-2026)</h2> <p>I&rsquo;m actually the second owner of this smartphone, my girlfriend used it for a few months starting in 2021, and it cost about $600. I switched to it in 2022 and am still using it now in 2026. That&rsquo;s easily the longest run of any phone I&rsquo;ve owned.</p> <p>I&rsquo;m still on the latest Android version, which is amazing, but it looks like Google phones still struggle with battery life. It used to last me all day and now I sometimes have to charge it before bed.</p> <p>Google hinted this current major Android update is the last, though I&rsquo;ll keep getting security patches for a year or two. In theory, that means this phone could last about seven years total. That&rsquo;s remarkable for Android, but iPhones have been doing this since inception, so it&rsquo;s more like the Android ecosystem finally catching up. The real win would be making that kind of long‑term support available on cheaper phones.</p> <h2 id="next-grapheneos-phone">Next: GrapheneOS Phone?</h2> <p>GrapheneOS has been gaining traction lately, but it only supports Pixel phones. They&rsquo;ve announced a new partnership that should expand the range of supported hardware this year, though. The developers aren&rsquo;t thrilled with Pixel&rsquo;s hardware direction, so I&rsquo;ll probably wait for non‑Pixel support before switching to a hardened, de‑Googled Android.</p> <p>It also helps that Russia doesn&rsquo;t rely on Google Services anymore, and my main travel focus right now is China, which isn&rsquo;t exactly Google‑friendly either. So the transition could be pretty smooth.</p> <h2 id="is-it-just-me">Is it Just Me?</h2> <p>It&rsquo;s surprisingly hard to find good smartphone sales data, but the sources I checked suggest sales have either plateaued or begun falling. This could be explained by one or both of these factors:</p> <ul> <li><strong>Market saturation:</strong> Almost everyone who wants a smartphone now has one, leaving little room for growth.</li> <li><strong>Diminishing upgrades:</strong> Higher prices and fewer meaningful innovations mean people hold onto their phones longer, extending the average device lifespan.</li> </ul> <h2 id="conclusion">Conclusion</h2> <p>It looks like the smartphone boom is over, and the market has changed a lot since the early days. I remember when a phone&rsquo;s price reliably signaled its quality, but that link has weakened in recent years. Global demand has slowed, and device lifespans may grow significantly, people will only pay so much.</p> <p>Rising smartphone prices have boosted profits for many manufacturers, but that model may not be sustainable if people start upgrading less often.</p> <p>It will be interesting to see how companies like Samsung and Apple fare in this perfect storm of slowing demand and longer device lifespans. They&rsquo;re trying to diversify, but they&rsquo;re still heavily tied to smartphone sales.</p> <p>One thing that has stayed relatively stable, though, is the <strong>price per smartphone per year</strong>. You can pick up a budget model for $150–$250, but it might only last two years. If you&rsquo;re willing to spend up to $1,000 or even more, you can get a phone that lasts seven years or longer. In the end, the annual cost often works out about the same.</p> 3 Body Problem https://bubelov.com/blog/2026/3-body-problem/ Wed, 04 Feb 2026 00:00:00 +0000 https://bubelov.com/blog/2026/3-body-problem/ <p>The On the Media podcast dedicated an episode to this show and Chinese sci‑fi in general. I didn&rsquo;t even know that was a thing, so I decided to check it out.</p> <p>It&rsquo;s a solid film. Not too nerdy, but still intellectually engaging. Some actors, like Marlo Kelly, are a perfect fit for their roles. I&rsquo;m looking forward to season two, which is in production now and expected by the end of this year.</p> The Communist Manifesto https://bubelov.com/blog/2026/the-communist-manifesto/ Wed, 04 Feb 2026 00:00:00 +0000 https://bubelov.com/blog/2026/the-communist-manifesto/ <h2 id="preface">Preface</h2> <p>One of Karl Marx&rsquo;s most famous quotes is actually inscribed on his tomb:</p> <blockquote> <p>The philosophers have only interpreted the world, in various ways; the point, however, is to change it.</p> </blockquote> <p>I think this is a good place to start if you want to understand Marx and his ideas.</p> <p>He&rsquo;s clearly flipping traditional philosophy on its head here. He argues that earlier philosophers, by focusing on interpreting the world of ideas, were just treating the symptoms and not the disease itself.</p> <p>For Marx, the foundational reality, the thing you have to interpret in order to change, is the material, economic organization of society (the &ldquo;base&rdquo;). So the only philosophy worth the name is one that ends in the revolutionary practice of transforming that material base.</p> <p>His ideas are meant to be acted on in this specific, revolutionary sense. That&rsquo;s what separates him from the Idealist tradition, which he saw as stuck in the abstract &ldquo;clouds&rdquo; of consciousness, even when it aimed for change.</p> <h2 id="quotes">Quotes</h2> <blockquote> <p>Communism is already acknowledged by all European Powers to be itself a Power.</p> </blockquote> <p>By 1848, every government in Europe was actively persecuting communists and socialist movements by banning publications, exiling leaders and infiltrating worker groups. For Marx, that repression was the best possible proof of communism&rsquo;s potency. Mobilizing against it meant acknowledging its power.</p> <blockquote> <p>The history of all hitherto existing society is the history of class struggles.</p> </blockquote> <p>Marx is arguing that throughout recorded history, from ancient empires to modern nations, every society has been fundamentally shaped by conflict between social groups with opposing economic interests.</p> <p>That might sound like common sense today, but there are plenty of alternative narratives. Some believe history is driven by &ldquo;great men&rdquo;, others that ideas shape the world as they spread, and some focus only on technological progress. What Marx does here is strip away the noise and focus on what he sees as the fundamental and contradictory engine of historical change: the struggle between those who control production and those whose labor they <strong>depend on</strong>.</p> <blockquote> <p>It has resolved personal worth into exchange value, and in place of the numberless and indefeasible chartered freedoms, has set up that single, unconscionable freedom - Free Trade.</p> </blockquote> <p>Marx is saying capitalism has turned personal worth into a price tag. It has replaced countless concrete, hard-won freedoms with the one dominant &ldquo;freedom&rdquo; of the market, which he sees as ruthless and unjust.</p> <p>Marx is also calling out a philosophical bait‑and‑switch. Capitalism celebrates &ldquo;negative freedom&rdquo;, or freedom <strong>from</strong> state interference (embodied in Free Trade), as the highest good. But this single, dominant freedom for capital systematically destroys the possibility of &ldquo;positive freedom&rdquo; for the worker: the freedom to develop as a human being, not just a commodity. What liberals call &ldquo;freedom&rdquo;, Marx calls the &ldquo;unconscionable freedom&rdquo; to reduce all human worth to a price and all social relations to a transaction.</p> <blockquote> <p>Conservation of the old modes of production in unaltered form, was, on the contrary, the first condition of existence for all earlier industrial classes. Constant revolutionizing of production, uninterrupted disturbance of all social conditions, everlasting uncertainty and agitation distinguish the bourgeois epoch from all earlier ones.</p> </blockquote> <p>In the past, stability was the key to survival. Under capitalism, constant chaos and change are the norm.</p> <blockquote> <p>In place of the old wants, satisfied by the productions of the country, we find new wants, requiring for their satisfaction the products of distant lands and climes.</p> </blockquote> <p>Marx is saying capitalism doesn&rsquo;t just satisfy local needs, it actively creates endless new desires that can only be met by pulling the whole world into its market. In other words, capitalism is unstable because it&rsquo;s not self‑sufficient. It must keep expanding, and it creates powerful incentives to expand by <strong>any means</strong>.</p> <blockquote> <p>Differences of age and sex have no longer any distinctive social validity for the working class. All are instruments of labour, more or less expensive to use, according to their age and sex.</p> </blockquote> <p>Under capitalism, workers aren&rsquo;t seen as men, women, or children with social roles. They&rsquo;re just tools, some cheaper (women, children), some more expensive (adult men), to be used up for profit.</p> <p>Marx is saying that capitalism is inherently <strong>anti-traditionalist</strong>, and that over time it dissolves all values except market value. Isn&rsquo;t it ironic that the post‑communist countries of Eastern Europe often seem more &ldquo;based&rdquo; today? Could it be that Marxism, in fact, shielded them from being fully dissolved into that &ldquo;no values except for market value&rdquo; system?</p> <blockquote> <p>All previous historical movements were movements of minorities, or in the interests of minorities.</p> </blockquote> <p>That&rsquo;s a bold statement. Marx claims that every major change in history before the 19th century was driven by, and ultimately benefited, a small elite, never the majority.</p> <p>It&rsquo;s clear that both Marx and <a href="https://bubelov.com/blog/2025/the-populist-delusion/">Mosca</a> agree that history is minority rule. Marx calls them economic classes while Mosca calls them organized elites. Marx sees transition happening through revolutionary class struggle, while elite theory often sees it as a mere circulation of elites.</p> <p>Bitcoin, in its current state, is also a minority movement. Marx would probably frame it as just another minority revolution waiting in the queue, hoping to dominate the majority.</p> <blockquote> <p>In this sense, the theory of the Communists may be summed up in the single sentence: Abolition of private property.</p> </blockquote> <p>The key here is understanding what &ldquo;private property&rdquo; actually means. The ultimate goal is to end the system where a minority class privately owns the productive assets that society depends on, things like factories, land, and machinery. Most personal possessions are out of scope. <strong>Your privately owned toothbrush doesn&rsquo;t interest a typical Marxist because toothbrushes aren&rsquo;t a basis for exploitation.</strong></p> <p>Since the proletariat is defined by having no property in the means of production and being forced to sell its labor, abolishing that property form abolishes the class relation itself.</p> <blockquote> <p>To be a capitalist, is to have not only a purely personal, but a social status in production.</p> </blockquote> <p>Marx is saying that being a capitalist means occupying a social office, not just a personal identity. Your private morals don&rsquo;t really matter here, you become the &ldquo;personification of capital&rdquo;, an agent through which the impersonal logic of profit and accumulation operates. If you deviate from that logic, say, by prioritizing workers&rsquo; welfare or ethics over competitive survival, you stop being a capitalist. The result is a sterile, homogenous system where only one imperative is tolerated: <strong>expand or die</strong>.</p> <blockquote> <p>The proletarians have nothing to lose but their chains. They have a world to win. WORKING MEN OF ALL COUNTRIES, UNITE!</p> </blockquote> <p>This is a cool emotional rallying cry and the strategic conclusion of Marx&rsquo;s whole argument. He&rsquo;s meticulously shown how capitalism strips workers of any ownership or security (&ldquo;nothing to lose&rdquo;), binding them only by the wage system (&ldquo;chains&rdquo;). When the status quo offers you nothing but bondage, you have everything to gain by breaking free.</p> <h2 id="conclusion">Conclusion</h2> <p>The Communist Manifesto is a short, straightforward read, and I think it&rsquo;s a good introduction to Marxism. I&rsquo;m not sure I&rsquo;m ready to dive deeper right now, there are other ideas I want to explore first.</p> <p>A lot of Marxist critiques hit close to home, similar to how behavioral economics challenges the idea of efficient markets. Criticism is important, but the hard part is building a sound and testable alternative model.</p> <p>The reality is, we don&rsquo;t have a single clear example of a country where the means of production aren&rsquo;t largely in private hands. The modern state is a hybrid, some capitalist free‑for‑all mixed with bits of a communist &ldquo;utopia&rdquo;. One thing is certain though, it&rsquo;s clearly impossible to deny Marx&rsquo;s influence on how we think and how our everyday world is structured.</p> BTC Map January Recap https://bubelov.com/blog/2026/btcmap-01/ Sun, 01 Feb 2026 00:00:00 +0000 https://bubelov.com/blog/2026/btcmap-01/ <p><strong>Note</strong>: this only reflects my personal work this week, visit <a href="https://blog.btcmap.org/">BTC Map Blog</a> to get consolidated monthly reports.</p> <h2 id="event-api">Event API</h2> <p>Events are still an experimental feature I added for fun in the Android app. The goal is to see if there&rsquo;s any organic demand for it.</p> <p>I personally maintain a few events in Asia, but we were recently contacted by the guy behind <a href="https://bitcoinwalk.org/">BitcoinWalk</a>, who wanted to sync our databases.</p> <p>The problem was, we didn&rsquo;t have an event API or event-scoped admin roles. So I built the missing endpoints, and they&rsquo;re now live. BitcoinWalk is already using the new API, which is why you can see dozens of new events in the BTC Map Android app.</p> <p>If you&rsquo;re a web or iOS developer and want to see events on your platform, pull requests are welcome.</p> <h2 id="payment-infra">Payment Infra</h2> <p>We care about privacy, but we also don&rsquo;t want spam, so we&rsquo;ve spent a lot of time messing with paywalls. Our apps don&rsquo;t require signups, but some stuff, like anonymous comments and boosts, will cost you a few sats. We weren&rsquo;t sure if users would bite, but we&rsquo;ve seen a steady trickle of paid invoices.</p> <p>Our old invoicing server is a total hack job though. It&rsquo;s unreliable and held together by digital duct tape. My main mission in January was to finally fix this mess.</p> <p>I looked at a few options and ended up setting up a new server running <code>Bitcoin Core/Knots</code> and <code>LND</code>. Our old setup used the LNBits API, which was always the weakest link. The other headache was <code>CLN</code>, which is a maintenance nightmare. I&rsquo;m a big fan of the single‑binary approach (thanks, <code>bitcoind</code>), and <code>LND</code> gets that right.</p> <p>The new server is live. I&rsquo;ll probably keep the old one around as a backup. By routing all new invoices to the new box, I can finally poke at the old one without breaking everything.</p> <h2 id="sharing-knowledge">Sharing Knowledge</h2> <p>I also wrote up my whole node setup quest in a <a href="https://bubelov.com/blog/2026/how-to-accept-bitcoin/">dedicated post</a>. I&rsquo;ll probably follow up with a few more deep‑dives, but this is a solid start.</p> How to Accept Bitcoin https://bubelov.com/blog/2026/how-to-accept-bitcoin/ Fri, 30 Jan 2026 00:00:00 +0000 https://bubelov.com/blog/2026/how-to-accept-bitcoin/ <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#who-this-guide-is-for">Who This Guide Is For</a></li> <li><a href="#why-bitcoin">Why Bitcoin</a></li> <li><a href="#expected-functionality">Expected Functionality</a></li> <li><a href="#highlevel-technical-overview">High‑Level Technical Overview</a></li> <li><a href="#part-1-preparing-the-server">Part 1: Preparing the Server</a></li> <li><a href="#part-1-bitcoind">Part 1: bitcoind</a> <ul> <li><a href="#getting-the-binary">Getting the Binary</a></li> <li><a href="#config">Config</a></li> <li><a href="#systemd-service">Systemd Service</a></li> <li><a href="#sync">Sync</a></li> </ul> </li> <li><a href="#part-2-lnd">Part 2: lnd</a> <ul> <li><a href="#getting-the-binary-1">Getting the Binary</a></li> <li><a href="#config-1">Config</a></li> <li><a href="#systemd-service-1">Systemd Service</a></li> <li><a href="#sync-1">Sync</a></li> </ul> </li> <li><a href="#part-3-inbound-liquidity">Part 3: Inbound Liquidity</a></li> <li><a href="#part-4-first-invoice">Part 4: First Invoice</a></li> <li><a href="#part-5-checking-invoice-status">Part 5: Checking Invoice Status</a></li> <li><a href="#part-6-further-reading">Part 6: Further Reading</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="who-this-guide-is-for">Who This Guide Is For</h2> <p>This guide is mainly for open‑source software developers who want to accept donations or sell paid services for bitcoins. Ideally, you should have basic Linux knowledge and some self‑hosting experience.</p> <p>You can also share your node with friends and other open‑source projects. Collectively owned nodes reduce costs, simplify maintenance, and let people without Linux ops skills integrate Bitcoin payments into their work, accepting payments from anywhere in the world with no exceptions and no red tape.</p> <h2 id="why-bitcoin">Why Bitcoin</h2> <p>A lot of developers I know have to work around sanctions or simply don&rsquo;t want to form legal entities or kneel to financial institutions. Open‑source software needs open‑source money. As you&rsquo;ll see, self‑hosting your own &ldquo;bank account&rdquo; is pretty easy. You stay in full control, no paperwork or permission needed to send or receive money.</p> <h2 id="expected-functionality">Expected Functionality</h2> <p>After finishing this guide, you’ll have a fully functional <a href="https://bitcoin.org/en/">Bitcoin</a> and <a href="https://lightning.network/">Lightning</a> node that can create and process invoices via a <a href="https://lightning.engineering/api-docs/api/lnd/">straightforward REST API</a>. You can also use this server to make Bitcoin payments or withdraw funds to your personal wallet.</p> <p>Fixed on‑chain addresses are great for donations, while programmable Lightning invoices let you integrate Bitcoin payments into your backend. With this setup, you can sell premium features or offer a hosted version of your FOSS software for a monthly fee, for example.</p> <h2 id="highlevel-technical-overview">High‑Level Technical Overview</h2> <p>My distro of choice for this setup is <a href="https://archlinux.org/">Arch Linux</a>, but you should be able to reproduce it on any popular Linux distro. They&rsquo;re all <a href="https://systemd.io/">Systemd</a>‑based, and that&rsquo;s the only hard dependency.</p> <p>The two moving parts (<code>bitcoind</code> and <code>lnd</code>) are self‑contained binaries, each wrapped in a Systemd service for easy daemon management and better observability.</p> <p>The core priority here is simplicity and easy maintenance, so I won&rsquo;t cover security hardening. For small amounts of money, that&rsquo;s often overkill. It&rsquo;s usually easier to sweep the funds out periodically than to be paranoid. There&rsquo;s no point in hardening a system that might only bring in $100 in donations and other payments, a harsh reality for many open‑source projects, especially early on.</p> <h2 id="part-1-preparing-the-server">Part 1: Preparing the Server</h2> <p>You&rsquo;ll need a server with a public IP address. Aim for at least 2 GB of RAM and ideally 2+ (v)CPUs. I&rsquo;ve run this setup on old, slow hardware before, and it worked fine. If you don&rsquo;t have enough RAM, adding swap will help you avoid the OOM killer.</p> <p>Provision a fresh server and log in via SSH as <code>root</code>. This guide prioritizes simplicity, so we won&rsquo;t create extra users or set up complicated permissions.</p> <p>Because we’ll use Let’s Encrypt, make sure you have a domain name that you can point to this server so HTTPS works from the start. This guide uses <code>lnd.example.com</code>, and you&rsquo;re supposed to change it to your actual domain.</p> <h2 id="part-1-bitcoind">Part 1: bitcoind</h2> <h3 id="getting-the-binary">Getting the Binary</h3> <p>You need to download a binary called <code>bitcoind</code> and place it in the <code>/usr/local/bin/</code> directory. You can also put the <code>bitcoin-cli</code> binary there, it&rsquo;s a handy admin interface for your node.</p> <p>For a detailed walkthrough on downloading Bitcoin binaries, check out one of my other posts: <a href="https://bubelov.com/blog/2025/bitcoin-29-unpacked/">Bitcoin 29 Unpacked</a>.</p> <h3 id="config">Config</h3> <p>Create <code>/root/.bitcoin/bitcoin.conf</code>:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Enable RPC but only allow local connections</span> </span></span><span class="line"><span class="cl"><span class="nv">server</span><span class="o">=</span><span class="m">1</span> </span></span><span class="line"><span class="cl"><span class="nv">rpcbind</span><span class="o">=</span>127.0.0.1 </span></span><span class="line"><span class="cl"><span class="nv">rpcallowip</span><span class="o">=</span>127.0.0.1 </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1"># Measured in MB</span> </span></span><span class="line"><span class="cl"><span class="c1"># Can be adjusted or disabled depending on available disk space</span> </span></span><span class="line"><span class="cl"><span class="nv">prune</span><span class="o">=</span><span class="m">25000</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1"># Improved Systemd integration</span> </span></span><span class="line"><span class="cl"><span class="nv">startupnotify</span><span class="o">=</span>systemd-notify --ready </span></span><span class="line"><span class="cl"><span class="nv">shutdownnotify</span><span class="o">=</span>systemd-notify --stopping </span></span></code></pre></div><h3 id="systemd-service">Systemd Service</h3> <p>Create <code>/etc/systemd/system/bitcoind.service</code>:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="o">[</span>Unit<span class="o">]</span> </span></span><span class="line"><span class="cl"><span class="nv">After</span><span class="o">=</span>network-online.target </span></span><span class="line"><span class="cl"><span class="nv">Wants</span><span class="o">=</span>network-online.target </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="o">[</span>Service<span class="o">]</span> </span></span><span class="line"><span class="cl"><span class="nv">Type</span><span class="o">=</span>notify </span></span><span class="line"><span class="cl"><span class="nv">ExecStart</span><span class="o">=</span>/usr/local/bin/bitcoind -datadir<span class="o">=</span>/root/.bitcoin </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="o">[</span>Install<span class="o">]</span> </span></span><span class="line"><span class="cl"><span class="nv">WantedBy</span><span class="o">=</span>multi-user.target </span></span></code></pre></div><h3 id="sync">Sync</h3> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">systemctl <span class="nb">enable</span> --now bitcoind.service </span></span></code></pre></div><p>Your Bitcoin node will start syncing. It stores all its data in <code>/root/.bitcoin/</code>, so expect that directory to grow steadily.</p> <p>Wait for your node to finish syncing. This can take anywhere from hours to days. Here’s how you can check the progress:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">bitcoin-cli getblockchaininfo </span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;chain&#34;</span><span class="p">:</span> <span class="s2">&#34;main&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;blocks&#34;</span><span class="p">:</span> <span class="mi">934319</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;headers&#34;</span><span class="p">:</span> <span class="mi">934319</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;bestblockhash&#34;</span><span class="p">:</span> <span class="s2">&#34;000000000000000000018b7caca393ae6b0e009828b03d91fbdc415d70d6abd2&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;bits&#34;</span><span class="p">:</span> <span class="s2">&#34;1701fca1&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;target&#34;</span><span class="p">:</span> <span class="s2">&#34;00000000000000000001fca10000000000000000000000000000000000000000&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;difficulty&#34;</span><span class="p">:</span> <span class="mf">141668107417558.2</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;time&#34;</span><span class="p">:</span> <span class="mi">1769759954</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;mediantime&#34;</span><span class="p">:</span> <span class="mi">1769757145</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;verificationprogress&#34;</span><span class="p">:</span> <span class="mf">0.9999960291979498</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;initialblockdownload&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;chainwork&#34;</span><span class="p">:</span> <span class="s2">&#34;00000000000000000000000000000000000000010b06672e15fc4dd7fd029150&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;size_on_disk&#34;</span><span class="p">:</span> <span class="mi">5399547383</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;pruned&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;pruneheight&#34;</span><span class="p">:</span> <span class="mi">931376</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;automatic_pruning&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;prune_target_size&#34;</span><span class="p">:</span> <span class="mi">26214400000</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;warnings&#34;</span><span class="p">:</span> <span class="p">[</span> </span></span><span class="line"><span class="cl"> <span class="p">]</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>You should check the <code>verificationprogress</code> field. If it’s close to <code>1.0</code>, your node is fully synced and you can move on to installing a Lightning daemon.</p> <h2 id="part-2-lnd">Part 2: lnd</h2> <p>Lightning implementations run on top of <code>bitcoind</code>, which is why we installed it first. Now it&rsquo;s time to add the Lightning layer, this enables cheap, instant payments and provides a straightforward REST API for integrating this borderless, permissionless system into your projects.</p> <h3 id="getting-the-binary-1">Getting the Binary</h3> <p>Grab the latest release from the official GitHub repo:</p> <p><a href="https://github.com/lightningnetwork/lnd/releases">https://github.com/lightningnetwork/lnd/releases</a></p> <p>Once you have the <code>lnd</code> binary, put it in <code>/usr/local/bin/</code>, the same directory where you placed <code>bitcoind</code> and <code>bitcoin-cli</code> in part 1. You should also put <code>lncli</code> there. It&rsquo;s like <code>bitcoin-cli</code>, but for Lightning.</p> <p>You&rsquo;ll need to run <code>lnd</code> manually (and without a config file) the first time to set a wallet password.</p> <h3 id="config-1">Config</h3> <p>Create <code>/root/.lnd/lnd.conf</code>:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="o">[</span>Application Options<span class="o">]</span> </span></span><span class="line"><span class="cl"><span class="nv">listen</span><span class="o">=</span>:9735 </span></span><span class="line"><span class="cl"><span class="nv">rpclisten</span><span class="o">=</span>127.0.0.1:10009 </span></span><span class="line"><span class="cl"><span class="nv">restlisten</span><span class="o">=</span>0.0.0.0:443 </span></span><span class="line"><span class="cl"><span class="nv">restlisten</span><span class="o">=[</span>::<span class="o">]</span>:443 </span></span><span class="line"><span class="cl"><span class="nv">letsencryptdomain</span><span class="o">=</span>lnd.example.com </span></span><span class="line"><span class="cl">wallet-unlock-password-file<span class="o">=</span>/root/.lnd/password.txt </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="o">[</span>Bitcoin<span class="o">]</span> </span></span><span class="line"><span class="cl">bitcoin.mainnet<span class="o">=</span><span class="nb">true</span> </span></span><span class="line"><span class="cl">bitcoin.node<span class="o">=</span>bitcoind </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="o">[</span>Bitcoind<span class="o">]</span> </span></span><span class="line"><span class="cl">bitcoind.rpccookie<span class="o">=</span>/root/.bitcoin/.cookie </span></span><span class="line"><span class="cl">bitcoind.rpcpolling<span class="o">=</span><span class="nb">true</span> </span></span></code></pre></div><p>Note that you need to create a file <code>/root/.lnd/password.txt</code> manually and put your wallet unlock password there.</p> <p>Change <code>lnd.example.com</code> to your actual domain or subdomain.</p> <h3 id="systemd-service-1">Systemd Service</h3> <p>Create <code>/etc/systemd/system/lnd.service</code>:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="o">[</span>Unit<span class="o">]</span> </span></span><span class="line"><span class="cl"><span class="nv">Requires</span><span class="o">=</span>bitcoind.service </span></span><span class="line"><span class="cl"><span class="nv">After</span><span class="o">=</span>bitcoind.service </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="o">[</span>Service<span class="o">]</span> </span></span><span class="line"><span class="cl"><span class="nv">Type</span><span class="o">=</span>notify </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="nv">ExecStart</span><span class="o">=</span>/usr/local/bin/lnd </span></span><span class="line"><span class="cl"><span class="nv">TimeoutStartSec</span><span class="o">=</span><span class="m">1200</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="nv">ExecStop</span><span class="o">=</span>/usr/local/bin/lncli stop </span></span><span class="line"><span class="cl"><span class="nv">TimeoutStopSec</span><span class="o">=</span><span class="m">3600</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="nv">Restart</span><span class="o">=</span>on-failure </span></span><span class="line"><span class="cl"><span class="nv">RestartSec</span><span class="o">=</span><span class="m">60</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="o">[</span>Install<span class="o">]</span> </span></span><span class="line"><span class="cl"><span class="nv">WantedBy</span><span class="o">=</span>multi-user.target </span></span></code></pre></div><h3 id="sync-1">Sync</h3> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">systemctl <span class="nb">enable</span> --now lnd.service </span></span></code></pre></div><p>I’m not sure if you <em>should</em> wait, but I usually let <code>lnd</code> run for a few minutes and check the logs to make sure it&rsquo;s healthy and there aren&rsquo;t any errors:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">journalctl --unit lnd.service --since <span class="s1">&#39;5m ago&#39;</span> </span></span></code></pre></div><h2 id="part-3-inbound-liquidity">Part 3: Inbound Liquidity</h2> <p>Lightning nodes need inbound liquidity to accept payments. You can ask a friend to open a channel to you, or just buy liquidity from one of the well‑known providers:</p> <p><a href="https://lnbig.com/#/">https://lnbig.com/#/</a></p> <p>Last time I checked, $50k worth of inbound liquidity costs about $10.</p> <h2 id="part-4-first-invoice">Part 4: First Invoice</h2> <p>See this page with examples in Javascript and Python:</p> <p><a href="https://lightning.engineering/api-docs/api/lnd/lightning/add-invoice/">https://lightning.engineering/api-docs/api/lnd/lightning/add-invoice/</a></p> <p>Here&rsquo;s a <code>curl</code> example:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -X POST <span class="se">\ </span></span></span><span class="line"><span class="cl"> -H <span class="s2">&#34;Grpc-Metadata-macaroon: 030303&#34;</span> <span class="se">\ </span></span></span><span class="line"><span class="cl"> -H <span class="s2">&#34;Content-Type: application/json&#34;</span> <span class="se">\ </span></span></span><span class="line"><span class="cl"> https://lnd.example.com/v1/invoices <span class="se">\ </span></span></span><span class="line"><span class="cl"> -d <span class="s1">&#39;{ &#34;value&#34;: 25, &#34;memo&#34;: &#34;test&#34;, &#34;expiry&#34;: 3600 }&#39;</span> </span></span></code></pre></div><p>On the first REST API call, <code>lnd</code> will try to get a TLS certificate from Let&rsquo;s Encrypt. Double‑check that your domain is set up and points to your server&rsquo;s IP address.</p> <p>Macaroons are essentially bearer tokens. Your lnd instance generated a set of default macaroons in <code>/root/.lnd/data/chain/bitcoin/mainnet/</code>.</p> <p>Different macaroons have different permissions. For invoices, I recommend using this one to follow the principle of least privilege:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">xxd -ps -u -c <span class="m">1000</span> /root/.lnd/data/chain/bitcoin/mainnet/invoices.macaroon </span></span></code></pre></div><p>You&rsquo;ll get a HEX string that you should put into the <code>Grpc-Metadata-macaroon</code> header when making REST API calls.</p> <h2 id="part-5-checking-invoice-status">Part 5: Checking Invoice Status</h2> <p>When you create an invoice, the JSON response returns an object with the following structure:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="err">#</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;r_hash&#34;</span><span class="p">:</span> <span class="s2">&#34;&lt;bytes&gt;&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;payment_request&#34;</span><span class="p">:</span> <span class="s2">&#34;&lt;string&gt;&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;add_index&#34;</span><span class="p">:</span> <span class="s2">&#34;&lt;uint64&gt;&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;payment_addr&#34;</span><span class="p">:</span> <span class="s2">&#34;&lt;bytes&gt;&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span></code></pre></div><p>You should pay attention to these two fields:</p> <ul> <li><code>r_hash</code> - This is the invoice ID, which you&rsquo;ll use to check the invoice status.</li> <li><code>payment_request</code> - This is the invoice itself, show it to your users as a string or QR code.</li> </ul> <p>The <code>r_hash</code> field is base64‑encoded, but the invoice lookup endpoint expects it in HEX. Convert it like this:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">echo</span> -n <span class="s2">&#34;r_hash&#34;</span> <span class="p">|</span> base64 -d <span class="p">|</span> xxd -ps -c <span class="m">64</span> </span></span></code></pre></div><p>API reference:</p> <p><a href="https://lightning.engineering/api-docs/api/lnd/lightning/lookup-invoice/">https://lightning.engineering/api-docs/api/lnd/lightning/lookup-invoice/</a></p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -H <span class="s2">&#34;Grpc-Metadata-macaroon: A012345&#34;</span> https://lnd.example.com/v1/invoice/fa045443 <span class="p">|</span> jq </span></span></code></pre></div><p>If the <code>state</code> field is <code>SETTLED</code>, the invoice has been paid and you can take whatever action you need.</p> <h2 id="part-6-further-reading">Part 6: Further Reading</h2> <p>If you want to get comfortable with <code>bitcoin-cli</code>, here&rsquo;s a great course:</p> <p><a href="https://github.com/BlockchainCommons/Learning-Bitcoin-from-the-Command-Line">https://github.com/BlockchainCommons/Learning-Bitcoin-from-the-Command-Line</a></p> <p>To build your LND skills, check out these links:</p> <p><a href="https://docs.lightning.engineering/">https://docs.lightning.engineering/</a></p> <p><a href="https://lightning.engineering/api-docs/category/api-reference/">https://lightning.engineering/api-docs/category/api-reference/</a></p> <p>If you&rsquo;re interested in hosting Lightning wallets for friends and other projects, take a look at this nice web‑wallet UI that runs on top of LND:</p> <p><a href="https://lnbits.com/">https://lnbits.com/</a></p> <h2 id="conclusion">Conclusion</h2> <p>While setting up a Bitcoin node isn&rsquo;t the easiest thing in the world, it&rsquo;s often simpler than dealing with traditional payment providers. It&rsquo;s also an indispensable tool for anyone serious about self‑sovereignty.</p> <p>The truth is, there are no real alternatives if you want to enable truly borderless payments with no exceptions. Open‑source software has no borders, and neither should open‑source money.</p> Battery Self-Discharge Rate Benchmark: Part 2 https://bubelov.com/blog/2026/self-discharge-month-later/ Tue, 27 Jan 2026 00:00:00 +0000 https://bubelov.com/blog/2026/self-discharge-month-later/ <p><a href="https://bubelov.com/blog/2025/self-discharge/">Previous post</a></p> <h2 id="initial-measurements-2025-12-23">Initial Measurements (2025-12-23)</h2> <ul> <li>IKEA LADDA AA 2450 mAh, 1.2 V | Voltage: 1.44 V (100% charge)</li> <li>IKEA LADDA AAA 750 mAh, 1.2 V | Voltage: 1.47 V (100% charge)</li> <li>Soshine AAA LiFePO4 280 mAh, 3.2 V | Voltage: 3.39 V (100% charge)</li> <li>Soshine AAA Li‑ion 350 mAh, 3.7 V | Voltage: 4.17 V (100% charge)</li> </ul> <h2 id="current-measurements-2026-01-27">Current Measurements (2026-01-27)</h2> <ul> <li>IKEA LADDA AA 2450 mAh, 1.2 V | Voltage: 1.32 V (?% charge)</li> <li>IKEA LADDA AAA 750 mAh, 1.2 V | Voltage: 1.32 V (?% charge)</li> <li>Soshine AAA LiFePO4 280 mAh, 3.2 V | Voltage: 3.34 V (?% charge)</li> <li>Soshine AAA Li‑ion 350 mAh, 3.7 V | Voltage: 4.12 V (?% charge)</li> </ul> <h2 id="conclusion">Conclusion</h2> <p>So far so good. The voltage readings show that all the tested batteries are still near a full charge. The safe takeaway is that they can all last at least one month. Let&rsquo;s see what happens three months after the initial charge.</p> Wi-Fi is the Best Protocol for Smart Lights https://bubelov.com/blog/2026/wifi-lights/ Mon, 19 Jan 2026 00:00:00 +0000 https://bubelov.com/blog/2026/wifi-lights/ <p>Once you&rsquo;ve had lights you can actually control, you know, brightness, color, the whole deal, there&rsquo;s zero chance you&rsquo;re going back to the dumb ones. The whole &ldquo;smart&rdquo; light scene is a total mess though, because it&rsquo;s split between Bluetooth, Zigbee, Wi‑Fi, Matter over Thread, and Matter over Wi‑Fi. Pick your poison.</p> <p>I have a bunch of Wi‑Fi lights (Phillips WiZ) and a whole zoo of Zigbee lights, from premium Phillips Hue all the way down to no‑name AliExpress brands for $3. Honestly, I like my Wi‑Fi WiZ lights much more.</p> <p>While meshes like Zigbee and Thread are pitched as the best choice because every new device supposedly strengthens your network, most homes don&rsquo;t actually need a mesh. In reality, it just adds trouble. This isn&rsquo;t just my take, it&rsquo;s <a href="https://www.home-assistant.io/connect/zbt-2/">basically how the industry is evolving</a>. Zigbee coordinators are quickly moving from tiny USB sticks to giant dildoes, centralizing the network around one powerful node.</p> <p>Another basic flaw with Zigbee is letting lights act as router nodes instead of end nodes. When a router node goes offline, which happens anytime someone uses a physical light switch to cut its power, it disrupts the whole network. Zigbee networks do self‑heal, but it takes time and makes the whole network slower and less predictable.</p> <p>Finding a good alternative isn&rsquo;t easy. Bluetooth has range issues, and &ldquo;naked&rdquo; Wi‑Fi lacks a standardized pairing and device control protocol, so you end up needing a special app for every vendor, which is a hard no.</p> <p>While I don&rsquo;t recommend &ldquo;naked&rdquo; Wi‑Fi, a lot of the newer Wi‑Fi lights do support a protocol called Matter. It acts as a common language between Wi‑Fi devices and smart home hubs. Since they all speak it, you don&rsquo;t need any vendor apps, and using physical switches won&rsquo;t break your network.</p> <p>That said, Zigbee and Thread mesh networks aren&rsquo;t going anywhere, there&rsquo;s no real alternative for battery-powered devices. But for everything that plugs into the wall, I prefer Matter over Wi‑Fi.</p> Chip War https://bubelov.com/blog/2026/chip-war/ Wed, 14 Jan 2026 00:00:00 +0000 https://bubelov.com/blog/2026/chip-war/ <h2 id="book-overview">Book Overview</h2> <p>Chip War was one of the few English-language books on China I could find in Phuket, so I picked it up recently. I wanted some background and a quick overview before my trips to China. I&rsquo;m particularly interested in tech, and I hoped this book would shed some light on recent developments.</p> <p>Unfortunately, there&rsquo;s barely any useful signal, it&rsquo;s more focused on a historical overview of the semiconductor industry. It does an okay job at that, but you&rsquo;ll only find it interesting if you know absolutely nothing about the topic.</p> <p>Chris Miller seems more focused on crafting a propaganda narrative than giving an objective, honest look at the history and latest trends in the semiconductor industry. The book is full of &ldquo;boomer takes&rdquo;, like equating Nazi and Soviet rule, and spreading smears about China. In this story, American companies are the only good guys, and everyone else, including their direct allies in Asia, isn&rsquo;t playing fair.</p> <p>The book is generally careful about making predictions, but the author can&rsquo;t help himself and suggests that the US and its allies will remain unchallenged, with a 10-year lead in semiconductors. That&rsquo;s a bold prediction. Let&rsquo;s see how it&rsquo;s holding up at the beginning of 2026.</p> <h2 id="energy">Energy</h2> <p>The US generates about 4.45 TWh of electricity, while China generates roughly 10.27 TWh. Clearly, China is in a stronger position for leadership in computing. Deploying hardware at scale is no easy task, and the US is actually falling behind China in this race.</p> <h2 id="cpus">CPUs</h2> <p>The book correctly notes that the 2020 sanctions on Huawei dealt a heavy blow to a leading Chinese CPU designer, but it failed to kill the company. As of 2026, Huawei is back, and its CPUs are competitive enough to be sold to discerning smartphone buyers.</p> <p>Huawei switched to a domestic SMIC fab. It&rsquo;s not cutting-edge, but it&rsquo;s certainly not 10 years behind TSMC either. Their latest mobile SoC is roughly three years behind the global frontier in raw performance. We can safely say that China has achieved self-sufficiency and will likely become globally competitive in the coming years.</p> <h2 id="ram">RAM</h2> <p>China can produce its own globally competitive RAM. Its current global market share stands at about 6%, which isn&rsquo;t even enough to meet domestic demand, but the technology is there and can be scaled quickly. CXMT&rsquo;s latest DDR5 offerings are on par with the global competition. Neither the US nor its proxies hold a technological advantage here, and they should prepare to lose market share.</p> <h2 id="storage">Storage</h2> <p>The last SSD I bought was a Lexar, guess who owns it and where it’s made? Chinese SSDs are highly competitive and already hold about 23% of the global market.</p> <h2 id="ai">&ldquo;AI&rdquo;</h2> <p>LLMs need vast amounts of energy and world‑class researchers, and China has both. DeepSeek was a &ldquo;Chinese Sputnik&rdquo; moment, and most LLM benchmarks show Chinese models are highly competitive. Some argue US models are slightly ahead, but even if true, we&rsquo;re talking a gap of weeks, not 10 years.</p> <h2 id="conclusion">Conclusion</h2> <p>Chip War is essentially a historical overview laced with cope and US propaganda. It&rsquo;s still a decent read if you want to understand the American perspective on this issue.</p> <p>My personal, normative opinion is that no single nation should hold a decisive edge in semiconductors. A global monopoly is the worst possible state for the world, so I&rsquo;m glad to see it being challenged. Ideally, I&rsquo;d like more nations to prioritize self-sufficiency and reduce their dependence on foreign powers.</p> BTC Map Weekly Recap https://bubelov.com/blog/2026/btcmap-weekly-01-11/ Sun, 11 Jan 2026 00:00:00 +0000 https://bubelov.com/blog/2026/btcmap-weekly-01-11/ <p><strong>Note</strong>: this only reflects my personal work this week, visit <a href="https://blog.btcmap.org/">BTC Map Blog</a> to get consolidated monthly reports.</p> <h2 id="discord-is-no-more">Discord is No More</h2> <p>I&rsquo;m proud to report that BTC Map is no longer dependent on Discord. I migrated the remainder of our channels and bots to our new Matrix space.</p> <p>We&rsquo;ve also removed the Discord links from the landing page footer. I guess we&rsquo;ll just let the old server slowly fade away.</p> <h2 id="android-clustering">Android Clustering</h2> <p>There&rsquo;s been good progress on a new clustering method for the Android app. The animations feel smooth, but there&rsquo;s a noticeable performance hit. I&rsquo;ll keep working with this API since the old one has been deprecated by MapLibre.</p> <h2 id="health-checks">Health Checks</h2> <p>The API health checks are working fine, but I noticed a few website health check failures. I&rsquo;ve added more logging, it looks like there are some SSL issues (curl exit code 35):</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">healthcheck<span class="o">[</span>898007<span class="o">]</span>: <span class="o">[</span>111B blob data<span class="o">]</span> </span></span><span class="line"><span class="cl">healthcheck<span class="o">[</span>898007<span class="o">]</span>: * IPv6: <span class="o">(</span>none<span class="o">)</span> </span></span><span class="line"><span class="cl">healthcheck<span class="o">[</span>898007<span class="o">]</span>: * IPv4: 75.2.60.5 </span></span><span class="line"><span class="cl">healthcheck<span class="o">[</span>898007<span class="o">]</span>: * Trying 75.2.60.5:443... </span></span><span class="line"><span class="cl">healthcheck<span class="o">[</span>898007<span class="o">]</span>: * ALPN: curl offers h2,http/1.1 </span></span><span class="line"><span class="cl">healthcheck<span class="o">[</span>898007<span class="o">]</span>: <span class="o">}</span> <span class="o">[</span><span class="m">5</span> bytes data<span class="o">]</span> </span></span><span class="line"><span class="cl">healthcheck<span class="o">[</span>898007<span class="o">]</span>: * TLSv1.3 <span class="o">(</span>OUT<span class="o">)</span>, TLS handshake, Client hello <span class="o">(</span>1<span class="o">)</span>: </span></span><span class="line"><span class="cl">healthcheck<span class="o">[</span>898007<span class="o">]</span>: <span class="o">}</span> <span class="o">[</span><span class="m">1554</span> bytes data<span class="o">]</span> </span></span><span class="line"><span class="cl">healthcheck<span class="o">[</span>898007<span class="o">]</span>: * SSL Trust Anchors: </span></span><span class="line"><span class="cl">healthcheck<span class="o">[</span>898007<span class="o">]</span>: * CAfile: /etc/ssl/certs/ca-certificates.crt </span></span><span class="line"><span class="cl">healthcheck<span class="o">[</span>898007<span class="o">]</span>: <span class="o">[</span>2.1K blob data<span class="o">]</span> </span></span><span class="line"><span class="cl">healthcheck<span class="o">[</span>898007<span class="o">]</span>: * TLS connect error: error:00000000:lib<span class="o">(</span>0<span class="o">)</span>::reason<span class="o">(</span>0<span class="o">)</span> </span></span><span class="line"><span class="cl">healthcheck<span class="o">[</span>898007<span class="o">]</span>: * OpenSSL SSL_connect: Connection reset by peer in connection to btcmap.org:443 </span></span><span class="line"><span class="cl">healthcheck<span class="o">[</span>898007<span class="o">]</span>: <span class="o">[</span>76B blob data<span class="o">]</span> </span></span><span class="line"><span class="cl">healthcheck<span class="o">[</span>898007<span class="o">]</span>: * closing connection <span class="c1">#0</span> </span></span><span class="line"><span class="cl">healthcheck<span class="o">[</span>898007<span class="o">]</span>: curl: <span class="o">(</span>35<span class="o">)</span> Recv failure: Connection reset by peer </span></span></code></pre></div><p>I&rsquo;m not entirely sure what&rsquo;s causing it, but it looks like the issue is on Netlify&rsquo;s side. I&rsquo;ll keep monitoring that curl call to collect more data.</p> My First Trip to China https://bubelov.com/blog/2026/china/ Mon, 05 Jan 2026 00:00:00 +0000 https://bubelov.com/blog/2026/china/ <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#preface">Preface</a></li> <li><a href="#preparations">Preparations</a></li> <li><a href="#arrival">Arrival</a></li> <li><a href="#first-stop-shanghai">First Stop: Shanghai</a> <ul> <li><a href="#hotel">Hotel</a></li> <li><a href="#hipster-bar">Hipster Bar</a></li> <li><a href="#winter-walk">Winter Walk</a></li> <li><a href="#propaganda-art">Propaganda Art</a></li> <li><a href="#the-bund">The Bund</a></li> <li><a href="#getting-around">Getting Around</a></li> <li><a href="#coffee-battle">Coffee Battle</a></li> </ul> </li> <li><a href="#trains">Trains</a></li> <li><a href="#second-stop-shenzhen">Second Stop: Shenzhen</a> <ul> <li><a href="#hotel-1">Hotel</a></li> <li><a href="#tech">Tech</a></li> <li><a href="#food">Food</a></li> <li><a href="#infrastructure">Infrastructure</a></li> </ul> </li> <li><a href="#third-stop-guangzhou">Third Stop: Guangzhou</a> <ul> <li><a href="#hotel-2">Hotel</a></li> <li><a href="#getting-around-1">Getting Around</a></li> <li><a href="#new-town">New Town</a></li> </ul> </li> <li><a href="#departure">Departure</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="preface">Preface</h2> <p>I rarely travel to countries that require a visa. Luckily, China became visa‑free for Russians starting in September 2025. My girlfriend and I had about ten free days over the New Year holidays, so we decided China would be a great place to explore.</p> <h2 id="preparations">Preparations</h2> <p>Fortunately, I have a few friends who live, or used to live, in China, so here&rsquo;s the &ldquo;travel pack&rdquo; I put together based on their advice:</p> <ul> <li><strong>Alipay</strong> mobile app - for payments</li> <li><strong>Amap</strong> app - for maps (Google Maps is outdated in China)</li> <li>Virtual Visa/MasterCard/UnionPay to link to Alipay</li> <li><strong>WeChat</strong> app for payments and networking with locals</li> <li><strong>Nihao China</strong> app - as a backup payment method</li> <li><strong>CCC-certified</strong> power bank</li> <li><strong>eSIM</strong> - very cheap, no firewall, can be bought inside Alipay or Nihao</li> <li><strong>Cash</strong> - just in case</li> </ul> <h2 id="arrival">Arrival</h2> <p>I filled out a digital arrival card before the flight, it&rsquo;s supposed to make immigration faster. It must have worked, because everything moved pretty quickly. The officer did ask, &ldquo;Why are you here?&rdquo;, which came off as a little aggressive, but I assume it&rsquo;s just standard. If they&rsquo;re required to ask, there are definitely friendlier ways to phrase it. They might stick to simple English on purpose, to avoid any misunderstanding.</p> <p>I landed in Shanghai and grabbed a cab from the airport taxi stand. The car was electric, but it smelled like an ashtray, smoking in cars is pretty common in China. You can avoid it by picking the &ldquo;premium&rdquo; option in Didi. Taxis here are cheap anyway, so premium became my go‑to after that.</p> <h2 id="first-stop-shanghai">First Stop: Shanghai</h2> <h3 id="hotel">Hotel</h3> <p>I stayed at an Atour hotel for the first three nights. It was a solid choice for the price (under $100). Even though Chinese two‑pin sockets look the same as Thai ones, I wish I&rsquo;d brought a three‑pin adapter, since some of the outlets were pretty loose and wobbly.</p> <p><figure> <a href="https://bubelov.com/blog/2026/china/atour_hu_d7530c02ac65ec5.webp"> <img src="https://bubelov.com/blog/2026/china/atour_hu_5ac8b839327a431.webp" alt="" /> </a> <figcaption>Wobbly socket</figcaption> </figure></p> <h3 id="hipster-bar">Hipster Bar</h3> <p>I arrived pretty late but decided to look for somewhere to eat and drink within walking distance before calling it a night. Luckily, I found a bar just a few steps from my hotel.</p> <p><figure> <a href="https://bubelov.com/blog/2026/china/hipster-bar_hu_c1c9d0e7996adf45.webp"> <img src="https://bubelov.com/blog/2026/china/hipster-bar_hu_dad526652110e903.webp" alt="" /> </a> <figcaption>Hipster bar</figcaption> </figure></p> <p>The place was run by local hipsters and felt very western, which is not what I expected in central China. The staff were young, heavily tattooed, and acted in a way that felt more western than Chinese. I later realized that wasn&rsquo;t the norm at all. They also spoke good English, which turned out to be rare in China.</p> <p>The only non‑western thing about the place was a BBQ stand right outside the door. I ordered a few skewers of beef and chicken. They were good, but a little bland compared to the spicy food I’m used to in Thailand.</p> <h3 id="winter-walk">Winter Walk</h3> <p>Shanghai in late December is cold, but not as cold as Moscow. The city is still surprisingly green, with roses and even some exotic trees scattered around. You can tell some of the plants need a little extra help to make it through the winter.</p> <p><figure> <a href="https://bubelov.com/blog/2026/china/winter-trees_hu_3b8053e75b58baed.webp"> <img src="https://bubelov.com/blog/2026/china/winter-trees_hu_ea3f64052a3f75ac.webp" alt="" /> </a> <figcaption>Trees in Shanghai</figcaption> </figure></p> <h3 id="propaganda-art">Propaganda Art</h3> <p>I love propaganda art, so I couldn&rsquo;t miss Shanghai&rsquo;s main propaganda museum. Finding it wasn&rsquo;t easy, it had been moved to a new location, and the word &ldquo;propaganda&rdquo; was removed from some signs. I get the feeling the government isn&rsquo;t thrilled with the name, but it still draws tourists, so they moved it somewhere less visible and toned it down a little.</p> <p><figure> <a href="https://bubelov.com/blog/2026/china/propaganda-1_hu_10a1d888304e97ba.webp"> <img src="https://bubelov.com/blog/2026/china/propaganda-1_hu_bae6a6a582a9bfbf.webp" alt="" /> </a> <figcaption>Cool merch</figcaption> </figure></p> <p><figure> <a href="https://bubelov.com/blog/2026/china/propaganda-2_hu_e67654fbd21e943d.webp"> <img src="https://bubelov.com/blog/2026/china/propaganda-2_hu_7fba9aa6f6ccb340.webp" alt="" /> </a> <figcaption>Legendary Korea Journal</figcaption> </figure></p> <p>The place was amazing, I even picked up a book with reproductions of their posters. Highly recommended.</p> <p><figure> <a href="https://bubelov.com/blog/2026/china/propaganda-3_hu_be0d44debd4858fd.webp"> <img src="https://bubelov.com/blog/2026/china/propaganda-3_hu_25c4bcd8b6755cff.webp" alt="" /> </a> <figcaption>Propaganda art</figcaption> </figure></p> <h3 id="the-bund">The Bund</h3> <p>The Bund is a nice must-see area in Shanghai. The architecture is pretty nice and I liked the food options. I stumbled on a nice local eatery ran by Muslims, and the owners were wearing obviously religious clothing. I guess all those stories about the percecution of Muslims are a bit overblown.</p> <p><figure> <a href="https://bubelov.com/blog/2026/china/me-shanghai_hu_91955a870b9e082.webp"> <img src="https://bubelov.com/blog/2026/china/me-shanghai_hu_83d90e4082395fae.webp" alt="" /> </a> <figcaption>Me in Shanghai</figcaption> </figure></p> <p>I wanted to visit the International Financial Center. It was on the other side of the river, so I decided to walk. The navigation app said it would take about 30 minutes, I figured there must be a bridge. Turns out the &ldquo;bridge&rdquo; was actually a ferry, but it was fun and super cheap. I paid the same way I&rsquo;d been paying for the subway: with the &ldquo;Transport&rdquo; button in Alipay.</p> <h3 id="getting-around">Getting Around</h3> <p>Getting around in China&rsquo;s tier‑1 cities is pretty straightforward. Besides cheap taxis, the subway is a great option, it&rsquo;s convenient and well‑run. The Amap app handles route planning and tracking reliably.</p> <p><figure> <a href="https://bubelov.com/blog/2026/china/route_hu_fc4345308ead4777.webp"> <img src="https://bubelov.com/blog/2026/china/route_hu_58379a0b5ab0dce6.webp" alt="" /> </a> <figcaption>Subway route tracking</figcaption> </figure></p> <h3 id="coffee-battle">Coffee Battle</h3> <p>I&rsquo;d heard Starbucks wasn&rsquo;t doing well in China, and now I see why. Chains like Starbucks and Costa are priced ridiculously high compared to a newer local chain called Luckin.</p> <p><figure> <a href="https://bubelov.com/blog/2026/china/luckin_hu_fa06d0c498cc53e9.webp"> <img src="https://bubelov.com/blog/2026/china/luckin_hu_e259fdf47802ca14.webp" alt="" /> </a> <figcaption>Luckin</figcaption> </figure></p> <p>Luckin&rsquo;s coffee tastes great and costs a fraction of the price you would pay in Starbucks. Their business model is smart, there are no counters or waiters, you can only order through their app. You can order ahead and pick it up as you pass by. I didn&rsquo;t mind waiting a couple of minutes either, the chain is extremely cost‑optimized and efficient.</p> <h2 id="trains">Trains</h2> <p>I took a seven‑hour fast train from Shanghai to Shenzhen to get a glimpse of the Chinese countryside. It looked decent, but I noticed most settlements have almost identical houses. That hints at a centralized development model, but I&rsquo;m just not sure whether it&rsquo;s orchestrated by the state or managed locally through some sort of collective farm system.</p> <p><figure> <a href="https://bubelov.com/blog/2026/china/countryside_hu_37b9013af1ed38ad.webp"> <img src="https://bubelov.com/blog/2026/china/countryside_hu_cf0851d8b2b58ccf.webp" alt="" /> </a> <figcaption>Countryside</figcaption> </figure></p> <p>It&rsquo;s not easy to impress a Russian with trains, but I can say Chinese trains are solid. The only thing I didn&rsquo;t like was the Starbucks monopoly!</p> <p><figure> <a href="https://bubelov.com/blog/2026/china/train_hu_a48309a48600611e.webp"> <img src="https://bubelov.com/blog/2026/china/train_hu_aa1676f7372471c5.webp" alt="" /> </a> <figcaption>Coffee in train</figcaption> </figure></p> <h2 id="second-stop-shenzhen">Second Stop: Shenzhen</h2> <h3 id="hotel-1">Hotel</h3> <p>I booked a room at the Meyo+ hotel, and it wasn&rsquo;t a great decision. I&rsquo;d ordered a room with a big window and a nice view, but their system messed up and they only had a slightly cheaper one with a weird, tiny window. They also tried to charge me for breakfast, even though it was supposed to be included. Sorting that out with staff who didn&rsquo;t speak English wasn&rsquo;t easy.</p> <h3 id="tech">Tech</h3> <p>Taking the Metro to Huaqiangbei dropped me right into the heartbeat of the world&rsquo;s electronics trade. Exiting the station, I was immediately flanked by towering malls, each floor a labyrinth of stalls overflowing with LEDs, components, and every tech gadget imaginable, sold in batches from tiny resistors to pallets of smartphones. The scale was overwhelming. I could easily spend weeks here and still not see everything, wandering from one specialized building to the next, each a vertical city dedicated to a different slice of hardware.</p> <h3 id="food">Food</h3> <p>I have to say the food options were pretty unusual, and the service isn&rsquo;t really set up for tourists. Most places don&rsquo;t have waiters, you scan a QR code at your table and order through your phone. Each table has its own unique code, so the staff knows where to bring the food. That&rsquo;s about as much service as you&rsquo;ll get.</p> <p>On the plus side, there&rsquo;s no tipping, and all prices are fair and clear.</p> <p>I did go to a few places with actual waiters, mostly international restaurants. But even then, the menus could get really strange, take a look at this wagyu and potato pizza:</p> <p><figure> <a href="https://bubelov.com/blog/2026/china/pizza_hu_914b23c6e2dfc6f5.webp"> <img src="https://bubelov.com/blog/2026/china/pizza_hu_b5388b00d30d887c.webp" alt="" /> </a> <figcaption>Pizza</figcaption> </figure></p> <h3 id="infrastructure">Infrastructure</h3> <p>Shenzhen is a new city, and it shows. It feels spacious, with clean and polished infrastructure. I enjoyed a few walks around town, but the one annoyance was the e‑scooters using pedestrian walkways. In some spots, it&rsquo;s hard to stay relaxed when those 100‑kg machines zip around you, beeping constantly.</p> <h2 id="third-stop-guangzhou">Third Stop: Guangzhou</h2> <h3 id="hotel-2">Hotel</h3> <p>This time I went with Holiday Inn Express, and it was solid. I&rsquo;d never seen 2.4‑meter beds in a hotel before, is that an American size or something? Anyway, the room was clean and had a nice city view.</p> <p><figure> <a href="https://bubelov.com/blog/2026/china/hotel-view_hu_eac5e2164660c52f.webp"> <img src="https://bubelov.com/blog/2026/china/hotel-view_hu_4f9bba13fd9734ef.webp" alt="" /> </a> <figcaption>Hoter View</figcaption> </figure></p> <p>Every hotel I stayed at in China had USB‑A and USB‑C ports, but I prefer using my own AC charger. It&rsquo;s faster and probably more secure.</p> <p><figure> <a href="https://bubelov.com/blog/2026/china/socket_hu_6b5975ccc09a2015.webp"> <img src="https://bubelov.com/blog/2026/china/socket_hu_906827617750f3a3.webp" alt="" /> </a> <figcaption>Socket</figcaption> </figure></p> <h3 id="getting-around-1">Getting Around</h3> <p>The Guangzhou subway is good, and Didi was always there when I got too lazy to walk. It&rsquo;s a huge city, and I wanted to see how an average person lives here. What surprised me is how little difference there was from Thailand, right down to the habit of eating dinner outside near a local night market.</p> <h3 id="new-town">New Town</h3> <p>New Town is my favorite district in Guangzhou, mostly because of its wide selection of international cuisine. It even has an Irish pub and I ended up going back more than once.</p> <p>I also explored some of the seaside routes and a few other areas. Overall, it&rsquo;s a nice city, but the contrast with Shenzhen is pretty striking. Shenzhen is clearly newer and a lot wealthier.</p> <h2 id="departure">Departure</h2> <p>Leaving China was as easy as entering. I booked a flight back to Phuket with a layover in Hong Kong. Flying just 150 km felt like taking a bus, it was over almost before it started.</p> <h2 id="conclusion">Conclusion</h2> <p>Things I liked:</p> <ul> <li>Infrastructure</li> <li>Trains</li> <li>Cultural and historical POIs</li> <li>Cleanliness</li> <li>Safety</li> <li>Affordability</li> </ul> <p>Things I didn&rsquo;t like:</p> <ul> <li>Food</li> <li>Poor English ability</li> <li>Mechanical, QR-based service</li> <li>E-bikes using pedestrian walkways</li> </ul> <p>Overall, it was a solid trip. I’ll be back when it’s warmer to explore northern China.</p> Battery Self-Discharge Rate Benchmark: Part 1 https://bubelov.com/blog/2025/self-discharge/ Tue, 23 Dec 2025 00:00:00 +0000 https://bubelov.com/blog/2025/self-discharge/ <h2 id="form-factors-and-voltage">Form Factors and Voltage</h2> <p>It&rsquo;s hard to imagine life without AA and AAA batteries. They power everything from TV remotes and <a href="https://www.ikea.com/us/en/p/produkt-milk-frother-black-30301167/">portable milk frothers</a> to tons of small IoT sensors.</p> <p>Standard AA/AAA batteries use alkaline chemistry and are rated at 1.5 V. You might also have heard of rechargeable batteries in the same size. Those usually work, but they&rsquo;re typically Ni‑MH and rated at 1.2 V.</p> <p>That voltage difference matters a lot for smart devices that report battery status. Many are hard‑wired for 1.5 V, and using 1.2 V rechargeables can trigger false low‑battery warnings. Some sensors are also voltage‑sensitive, so I wouldn&rsquo;t recommend rechargeables unless you&rsquo;re sure the device supports them.</p> <p>So we know AA and AAA sizes don&rsquo;t define chemistry, or even expected voltage. And it gets even more mixed up. I recently ordered two other varieties:</p> <ul> <li>AAA Li‑ion 3.7 V</li> <li>AAA LiFePO4 3.2 V</li> </ul> <h2 id="self-discharge-rate">Self-Discharge Rate</h2> <p>Batteries aren&rsquo;t perfect, they can&rsquo;t hold a charge forever, even if you don&rsquo;t use them. That&rsquo;s why some people have a bad experience with rechargeable batteries in their remotes. Remotes are low-power devices, and most of their power draw can come from the battery&rsquo;s self‑discharge. Some cheap Ni‑MH batteries lose 10% or more of their charge per month. Putting those in a remote is a terrible idea, because you&rsquo;ll have to recharge them every few months.</p> <p>My rechargeable batteries are IKEA LADDA, which are high‑quality and rumored to be rebranded Eneloops. They&rsquo;re supposed to last for years, but I wanted to test that myself. I&rsquo;m also adding Li‑ion and LiFePO4 batteries to the mix to compare self‑discharge rates between different chemistries. Both of those are made by Soshine, which is a solid brand.</p> <h2 id="initial-measurements">Initial Measurements</h2> <p>Tested Batteries:</p> <ul> <li>IKEA LADDA AA 2450 mAh, 1.2 V | Voltage: 1.44 V (100% charge)</li> <li>IKEA LADDA AAA 750 mAh, 1.2 V | Voltage: 1.47 V (100% charge)</li> <li>Soshine AAA LiFePO4 280 mAh, 3.2 V | Voltage: 3.39 V (100% charge)</li> <li>Soshine AAA Li‑ion 350 mAh, 3.7 V | Voltage: 4.17 V (100% charge)</li> </ul> My Powerbank is Illegal in China https://bubelov.com/blog/2025/ce-ccc/ Tue, 23 Dec 2025 00:00:00 +0000 https://bubelov.com/blog/2025/ce-ccc/ <p>TIL the &ldquo;CE&rdquo; mark is basically a self-assigned &ldquo;trust me, bro&rdquo; promise, and it&rsquo;s not going to cut it if you&rsquo;re traveling to China.</p> <p>Turns out, China has a mandatory safety certification, the CCC, which sets a much higher bar. It requires independent product testing and annual factory inspections.</p> <p>Now that I have visa-free entry, I&rsquo;ll be visiting more often. For peace of mind, I picked up a new power bank with both CE and CCC marks. It&rsquo;s a solid upgrade since my old one only delivered 30W PD, but this one pushes 65W.</p> BTC Map Weekly Recap https://bubelov.com/blog/2025/btcmap-weekly-12-21/ Sun, 21 Dec 2025 00:00:00 +0000 https://bubelov.com/blog/2025/btcmap-weekly-12-21/ <p>Note: this only reflects my personal work this week.</p> <h2 id="rpc-log">RPC Log</h2> <p>We still have a few Discord hooks that fire when sensitive admin RPC calls are made. Since we&rsquo;re moving from Discord to Matrix, I spent some time reviewing those ad‑hoc notifications and realized our current approach doesn&rsquo;t scale well.</p> <p>So I created a dedicated table to log all RPC calls. This enables a few new uses. We can still monitor the log and send Matrix notifications for critical events, but it&rsquo;s also a fully structured storage that we can expose via the same API. That opens a path to building our own admin web interface.</p> <p>Storing the log in a structured, platform‑agnostic way is a solid improvement, and it also lets us simplify the code quite a bit.</p> <h2 id="health-checks">Health Checks</h2> <p>We&rsquo;ve always had a hard time tracking whether our most critical systems were actually up. We used to rely on an external &ldquo;uptime bot&rdquo;, which caused constant headaches with false positives and sneaky, almost DDoS‑like behavior.</p> <p>I ended up writing a few scripts that probe the health of our key components. Now if something goes down, the script can send a notification straight to Matrix.</p> <h2 id="organize-rpc-calls">Organize RPC Calls</h2> <p>The <code>rpc</code> module is a mess, so I spent time cleaning it up. The work is far from done, but at least there&rsquo;s progress!</p> ./projects https://bubelov.com/blog/2025/projects-folder/ Fri, 19 Dec 2025 00:00:00 +0000 https://bubelov.com/blog/2025/projects-folder/ <p>When I got my first programming job almost a couple of decades ago, I was put under the supervision of a senior programmer. I tried to learn and copy as much as possible from him. Vladimir was a pretty chill guy, and he hated my initial workaholic mindset, I even overheard him trying to get rid of me. I&rsquo;m a quick learner, so I figured out I needed to take a week for tasks that took a day, and to stop bothering people and asking for advice all the time. We got on good terms later on.</p> <p>One habit I picked up from him was putting all my code into a dedicated <code>projects</code> folder. We didn&rsquo;t use <code>git</code> back then, it was <code>svn</code> times, but ever since, one of the first things I do on a new machine is create that <code>~/projects</code> folder and clone my repos there.</p> <p>I have a few machines, and each one has its own <code>projects</code> folder. It&rsquo;s not ideal, and they often get out of sync. Plus, I have to check all the subfolders for stale diffs and unpushed commits whenever I want to wipe my OS and try something new.</p> <p>As I <a href="https://bubelov.com/blog/2025/nas/">recently mentioned</a>, I now have a portable encrypted drive, so I finally renamed my <code>projects</code> folder to <code>git</code> and moved it there. I also added a symlink so the folder is accessible from <code>~/git</code>.</p> <p>The new name is more specific, and it doesn&rsquo;t conflict with the existing <code>projects</code> folder, which I keep for non‑code project resources.</p> <p>This setup works because I only work from one machine at a time, and the portable drive is truly portable, easy to plug into whatever machine I&rsquo;m using.</p> <p><figure> <a href="https://bubelov.com/blog/2025/projects-folder/ssd_hu_405de92726e6ea2a.webp"> <img src="https://bubelov.com/blog/2025/projects-folder/ssd_hu_396dd345b8c83bdf.webp" alt="" /> </a> <figcaption>Portable disk</figcaption> </figure></p> <p>As a bonus, having a global &ldquo;proxy folder&rdquo; for my GitHub repos adds another layer of protection from deplatforming. People often forget that a <code>git</code> repo is just a self-contained directory.</p> BTC Map Weekly Recap https://bubelov.com/blog/2025/btcmap-weekly-12-14/ Sun, 14 Dec 2025 00:00:00 +0000 https://bubelov.com/blog/2025/btcmap-weekly-12-14/ <p>Note: this only reflects my personal work this week.</p> <p>Overall, it was a quiet week with little activity. Here are the main highlights:</p> <h2 id="improve-startup-time">Improve Startup Time</h2> <p>I&rsquo;m gradually moving BTC Map API chat automations from Discord to Matrix. The first experimental Matrix integration was blocking and significantly increased API server startup time (from ~2 seconds to ~10+ seconds). This week I set up an asynchronous lazy initialization, and it seems to be working well.</p> <h2 id="drop-boosts-table">Drop Boosts Table</h2> <p>Boosts now use the REST API and can be performed anonymously. That means it&rsquo;s not a good idea to rely on the old boosts table, which is tied to admin RPC and requires specific fields like an admin user ID. It was causing issues in the internal reporting, so I decided to remove the table entirely. The generic <code>invoice</code> table is sufficient and is already fully integrated into our REST API. Anonymous users can request invoices for sat-walled actions like leaving a comment or boosting a merchant. Once those invoices are paid, we perform the associated actions directly, no extra tables are needed, and we can rely on invoice metadata alone.</p> <h2 id="organize-rpc-calls">Organize RPC Calls</h2> <p>The <code>rpc</code> module is a mess, so I spent time cleaning it up. The work is far from done, but at least there&rsquo;s progress!</p> ESP & Rust: A Match Made in Heaven https://bubelov.com/blog/2025/esp-rust/ Sun, 14 Dec 2025 00:00:00 +0000 https://bubelov.com/blog/2025/esp-rust/ <p>One of my slow-going hobbies is tinkering with microcontrollers.</p> <p>It all started when I was a child. During a summer school break in my grandparents&rsquo; village, I found a huge box of electrical components. The box belonged to my uncle, who was apparently into this stuff. Needless to say, I didn&rsquo;t understand a thing and broke a lot of it, but the whole process fascinated me and made me curious about how things work under the hood.</p> <p>My second attempt to figure out what those fancy electronic components do came in 2018. I bought an Arduino board with a starter kit and a book. It was a fun read, and I grasped the basics. But this hobby is slow-going, so I put things on pause again.</p> <p>The third attempt was in 2020, right after the Raspberry Pi 4 was released. I got interested and ended up ordering six boards in total. It was a great experience, I picked up some essential sysadmin skills and got into self-hosting, which motivated me to keep blogging once my interest in finance cooled off and I didn&rsquo;t know what else to write about.</p> <p>The fourth and latest attempt to dive into electronics and microcontrollers started this year, in 2025, and it&rsquo;s still going. ESP is all the rage now, so I ordered a bunch of <a href="https://wiki.seeedstudio.com/xiao_esp32c6_getting_started/">Seeed Studio XIAO ESP32C6</a> boards. They&rsquo;re really nice. I started with Arduino but quickly outgrew it, which begs the question: what&rsquo;s next?</p> <p>The first, officially recommended option is ESP-IDF, which is a heavy and invasive pile of crap you have to install, and you have to write in C or something like that. That&rsquo;s a pretty bad option, honestly, but it does offer much more control over the board, so it&rsquo;s clearly better than staying on Arduino.</p> <p>The second and unexpected option was discovering <a href="https://docs.espressif.com/projects/rust/book/">The Rust on ESP Book</a>. Rust is a modern language I&rsquo;m familiar with, and it turns out I can use my standard Rust workflow and tools to write ESP firmware. At first, I thought you still needed ESP-IDF installed, but after looking into it more, I realized it&rsquo;s completely standalone.</p> <p>This combo is really exciting, and I’ve already started working on two projects:</p> <p><a href="https://github.com/bubelov/temp-hum-sensor">temp-hum-sensor</a></p> <p><a href="https://github.com/bubelov/pm-sensor">pm-sensor</a></p> <p>The first one is a simple temperature and humidity sensor using multiplexed I2C, and the second is a PM2.5 sensor using UART. Both I2C and UART are essential building blocks if you want your board to interact with external components, and I wanted to make sure they work smoothly in my Rust setup.</p> <p>The next step is adding Zigbee or Matter support so I can integrate my DIY sensors into my Home Assistant smart home. Exciting times ahead!</p> Why I Ditched My NAS for a USB Stick https://bubelov.com/blog/2025/nas/ Thu, 11 Dec 2025 00:00:00 +0000 https://bubelov.com/blog/2025/nas/ <p>So I&rsquo;m a happy owner of a GL Inet Flint 2 router, and it&rsquo;s a great piece of hardware. Besides routing, it supports tons of extensions like an ad blocker, VPN client or server, and network file storage.</p> <p>The router has a USB Type‑A port with a max bandwidth of 5 Gbps, which is not bad, since its fastest LAN port is only 2.5 Gbps. I had a few spare SSD sticks lying around, so I grabbed a Ugreen SSD to USB adapter and hooked up my drive. The Flint 2 firmware only supports SMB and WebDAV, so I went with SMB, since it&rsquo;s less exotic and works almost everywhere. I wanted to try NFS, but no luck.</p> <p>Most things worked fine, so I moved my personal data folder and my Kodi media library over to it. Then the issues started. Heavy 4K HDR movies struggled, and I got random disconnects when accessing my personal data. That&rsquo;s just not acceptable, so I started looking for other options.</p> <p>First I tried a standalone NAS. It fixed the stability problems, but 4K playback still lagged, the bottleneck turned out to be Kodi&rsquo;s Android app and its poor SMB client. Switching to NFS solved the issue, but I still wasn&rsquo;t happy with the speed or the extra maintenance.</p> <p>In the end, I ditched the NAS altogether and switched to local storage. I plugged a 1 TB stick directly into my TV box and got a simple, zero setup 10 Gbps link with no issues, which is a huge improvement over a status quo. The downside is I have to unplug the drive and connect it to my PC when I want to manage files, but the time lost is more than made up for by that outstanding 10 Gbps link.</p> <p>For my personal folder, I did the same thing: I set up an encrypted external SSD that I can take with me when I travel. That way I don&rsquo;t need remote access, and the data stays encrypted when it&rsquo;s not in use, a big security upgrade.</p> <p>Some people might really need remote access and extra features, but after looking at how I actually use my setup, overengineering it doesn&rsquo;t make sense. Keeping it simple works best, and “a USB stick with files” is a clean abstraction anyone can understand and use, even when I&rsquo;m not home.</p> Espresso Machines https://bubelov.com/blog/2025/espresso/ Wed, 10 Dec 2025 00:00:00 +0000 https://bubelov.com/blog/2025/espresso/ <p><a href="https://bubelov.com/blog/2023/french-press/">Previous post</a></p> <p>Like I mentioned last time, I got bored with the French press and wanted to try making espresso. These days, it&rsquo;s not as hard or expensive as it used to be, you need a good burr grinder, but those have gotten pretty affordable. Machines basically split into two kinds: manual and automatic.</p> <p>Flair (~$300+) is the top dog in manual espresso. I&rsquo;ve used one for a couple of years, and it&rsquo;s solid, but mine&rsquo;s starting to show its age. Replacement parts are surprisingly pricey, but the coffee is great. It takes some effort though, I usually spend about 20 minutes making coffee for me and my girlfriend. Most people aren&rsquo;t going to want to put in that kind of time.</p> <p>I also tried a new automatic machine from Duchess (~$150), and honestly, I can barely tell the difference. It saves me a ton of time and is way easier to learn. So right now, my setup is a separate automatic grinder and a separate automatic espresso maker. There are all-in-one machines that can steam milk too, but every one I&rsquo;ve tried made bad coffee, wasn&rsquo;t adjustable, and was generally unreliable.</p> Enter the Matrix https://bubelov.com/blog/2025/enter-the-matrix/ Mon, 08 Dec 2025 00:00:00 +0000 https://bubelov.com/blog/2025/enter-the-matrix/ <p>We&rsquo;ve been using Discord at BTC Map for quite a while, but it was never smooth sailing. The first time I tried to set up an account, it was immediately blocked. This could have been due to a Russian phone number, a Linux user agent, or both. There was no appeal and no explanation. It worked the second time with a Thai phone number and the official Android app, but it became pretty clear that we needed to migrate to something better in the long run.</p> <p>The second irritant arrived recently when Discord decided to enforce some questionable age verification laws. BTC Map is all about defying KYC, so this kind of development was totally unacceptable, and we started researching alternatives.</p> <p>Matrix was my favorite, and most people on the team also liked it. None of us had any serious experience with it, so it was a bit of a leap of faith. We&rsquo;ve already partially migrated, and I must say it&rsquo;s a pretty cool thing.</p> <p>Although we signed up for a public server, the Matrix protocol doesn&rsquo;t have a deplatforming risk, since we can self-host if we need to. Having such an option is crucial, but it doesn&rsquo;t mean it should be used by default. I&rsquo;m currently the only ops person and I simply don&rsquo;t have the time to self-host and maintain all the services we rely on. I also don&rsquo;t think BTC Map is controversial enough to worry about being deplatformed from a public server that hosts many other FOSS projects.</p> <p>Aside from deplatforming risk mitigation, Matrix also supports E2E encryption, which is a pretty big deal for a growing project dabbling in donations and some stealth/surprise campaigns like the Square integration.</p> <p>Finally, Matrix provides a robust bot/automation API. Working with the Matrix SDK is much more pleasant than being limited by Discord&rsquo;s webhooks.</p> What is BTC Map https://bubelov.com/blog/2025/what-is-btcmap/ Sat, 08 Nov 2025 00:00:00 +0000 https://bubelov.com/blog/2025/what-is-btcmap/ <h2 id="intro">Intro</h2> <p>I&rsquo;ve become aware that people sometimes claim to be associated with or even call themselves &ldquo;founders&rdquo; of BTC Map. Luckily, there is an easy way to spot impostors.</p> <p>BTC Map is an umbrella term covering two main efforts: gathering data and writing code to interact with that data.</p> <h2 id="part-1-data">Part 1: Data</h2> <p>People working on data are busy adding new merchants and re-verifying existing ones from time to time. This is a crucial task, and you can always see the most active contributors here:</p> <p><a href="https://btcmap.org/leaderboard">https://btcmap.org/leaderboard</a></p> <h2 id="part-2-code">Part 2: Code</h2> <p>While having accurate data is crucial, users need a way to access it conveniently. BTC Map&rsquo;s software infrastructure consists of:</p> <ul> <li>API (<a href="https://github.com/teambtcmap/btcmap-api/graphs/contributors">contributors</a>)</li> <li>Web App (<a href="https://github.com/teambtcmap/btcmap.org/graphs/contributors">contributors</a>)</li> <li>Android App (<a href="https://github.com/teambtcmap/btcmap-android/graphs/contributors">contributors</a>)</li> </ul> <h2 id="conclusion">Conclusion</h2> <p>Keep in mind that BTC Map has no legal entity, so there are obviously no founders or official representatives. If you want to avoid being scammed, you should contact the code or data maintainers in our public chats or public repos.</p> The Populist Delusion https://bubelov.com/blog/2025/the-populist-delusion/ Tue, 07 Oct 2025 00:00:00 +0000 https://bubelov.com/blog/2025/the-populist-delusion/ <p><a href="https://www.imperiumpress.org/shop/populist-delusion/">https://www.imperiumpress.org/shop/populist-delusion/</a></p> <p>Author: <a href="https://www.academic-agency.com/">Neema Parvini</a></p> <p>Really interesting book, although pretty cynical. Here are a few quotes that I think capture its essence. Definitely grab a copy if you&rsquo;re into this kind of thing.</p> <blockquote> <p>Since there are always the rulers and the ruled, how can ‘the people’ ever be sovereign? Power does not rest nor will ever rest in ‘the will of the people’, but rather in the organised efforts of the ruling minority.</p> </blockquote> <p>That&rsquo;s the main idea, and it rings true. Since the ruling class is always a minority, they stay in power by being highly organized and relying heavily on propaganda.</p> <blockquote> <p>In elections, as in all other manifestations of social life, those who have the will and, especially, the moral, intellectual and material means to force their will upon others take the lead over the others and command them.</p> </blockquote> <p>No doubt.</p> <blockquote> <p>However, feudal states are inefficient at quickly mobilising men for military campaigns and are subject to internal quarrels between rival lords. In contrast, the bureaucratic state, which has succeeded in centralising taxation, has greater specialization of the key functions of government and can maintain a standing army.</p> </blockquote> <p>This is basically the mainstream take on the evolution of the state.</p> <blockquote> <p>If people want change even at a time of popular and widespread resentment of the ruling class, they can only hope to achieve that change by becoming a tightly knit and organised minority themselves and, in effect, displacing the old ruling class.</p> </blockquote> <p>Yep, matches my observations. An unorganized opposition is a total clown show.</p> <blockquote> <p>Here, Pareto’s analysis bears many similarities with Mosca’s in terms of the fact that the elite are constantly replenished by exceptional individuals from the lower classes, and risk overthrow if they are too exclusive.</p> </blockquote> <p>That was definitely the main cause of the 1917 revolution in Russia.</p> <blockquote> <p>&lsquo;For the will of the people is not transferrable, nor even the will of the single individual’, argues Michels, drawing on Mosca directly, ‘in actual fact, directly the election is finished, the power of the mass over the delegate comes to an end.’ Hence not only is direct democracy impossible, but also representative democracy is necessarily a fiction.</p> </blockquote> <p>Yep, broken promises go hand in hand with the electoral process.</p> <blockquote> <p>The nature of organization is such that it gives power and advantages to the group of leaders who cannot then be checked or held accountable by their followers. Michels himself put it even more succinctly: ‘Who says organization, says oligarchy.</p> </blockquote> <blockquote> <p>Thus, once a leader has attained power in the first place, they are driven by something like a Nietzschean Will to Power, they are intoxicated by it and want more of it. It is significant that it is power that is the motivation and not merely money.</p> </blockquote> <p>This is often understated. People tend to portray politicians as just greedy, which is partly true, but there are more important factors at play.</p> <blockquote> <p>British politicians have a remarkable capacity to ‘fail upwards’. These politicians may have been voted out of their seats, but they remained part of the ruling class and enlarged the scope of their personal power.</p> </blockquote> <p>This proves the political class is just a private club. It&rsquo;s impossible to get kicked out, you just get shuffled to a different top spot.</p> <blockquote> <p>He also notes that the old leaders will style themselves as the sensible people, the ‘adults in the room’ against ‘extremists’ whom they can paint as naïvely idealist or as demagogues, and in this they can rely on the natural conservatism of the masses in the party membership (who distrust newcomers) to enlist support.</p> </blockquote> <blockquote> <p>This will likely appear absurd to some. How could an absolutist monarch bear any relation to, for example, the US government with its careful system of checks and balances, its separation of the executive from the legislature and judiciary and so on? The answer lies in the fact that Schmitt saw it fit to judge any political system not by its norms but when it was under crisis.</p> </blockquote> <p>Exactly. Systems reveal their true nature under pressure. The US championed free trade but has turned to protectionism. In Europe, self-proclaimed liberals are now harassing any Russian they can find, regardless of individual guilt. It exposes the elite&rsquo;s &ldquo;whatever it takes&rdquo; mentality when their goals are threatened.</p> <blockquote> <p>From the realist perspective of Schmitt, there is no structural difference between the liberal state, the communist state, and the fascist state, or indeed any other state. The only difference is the extent to which a regime may obscure the nature of its power or else genuinely buy into myths of neutrality. Viewed in this way, a state wedded to liberal democracy is as ‘totalitarian’ as any other since, by its very nature, it will be unable to tolerate any leaders who are not always already liberal democrats.</p> </blockquote> <p>We can see it clearly in the way current EU elites are harassing a genuinely popular opposition.</p> <blockquote> <p>England, too, discovered the ideal of a Free Press, and discovered along with it that the press serves him who owns it. It does not spread ‘free’ opinion, it generates it.</p> </blockquote> <p>After following the BBC and The Guardian for a while, I have to agree. There&rsquo;s no such thing as an objective, government-supported, or even just government-tolerated media.</p> <blockquote> <p>Liberalism, in its degenerate and anarchic form involving periodic crises and unemployment as in the U.S.A., implies even greater subjection of the individual to economic forces than does Communism.</p> </blockquote> <blockquote> <p>The moment of truth for any regime will come at the moment in which ideological ‘soft power’ is stripped away and it must use repressive force to crush its opposition. Hesitancy on the part of Power at the hour of decision, whether through a failure of nerve in the leadership or a failure in confidence on the part of their generals, will seal their fate if rival aristocrats exploit popular discontent.</p> </blockquote> <blockquote> <p>The nineteenth-century liberals overlooked, and the twentieth-century liberals decline to face, the fact that teaching everyone to read opens minds to propaganda and indoctrination at least as much as to truths.</p> </blockquote> <p>Absolutely. Centralization boosts group coherence, and we have more tools for it now than ever before.</p> <blockquote> <p>The divorce of control, or power, from ownership has been due in large part to the growth of public corporations. So long as a single person, family or comparatively small group held a substantial portion of the common shares of a corporation, the legal ‘owners’ could control its affairs. Even if they no longer actually conducted the business, the operating managers were functioning as their accountable agents.</p> </blockquote> <p>Now we&rsquo;re switching to the topic of &ldquo;managerialism.&rdquo; But it should be obvious to anyone who wasn&rsquo;t born yesterday that corporations are just arms of the government, and the state won&rsquo;t tolerate any dissent.</p> <blockquote> <p>The same can be said, and doubly so, for the Ford Foundation. Shortly after Henry Ford’s death, Henry Ford II signed a document stating that the Ford family would exercise no more influence over the foundation than any other board member; he regretted the decision for the rest of his life. Since then, the Ford Foundation has supported almost exclusively left-wing progressive causes that would make Henry Ford—a well-known social conservative—turn in his grave.</p> </blockquote> <blockquote> <p>Brendan Eich was forced to resign after only eleven days as CEO of Mozilla after it was found he had donated to a political campaign against gay marriage and employees launched a social media campaign to oust him.</p> </blockquote> <p>Luckily for him, he founded a successful company called Brave after that.</p> <blockquote> <p>As more people come to see them as unmistakably totalitarian in nature, and as the gap between elite and popular values widens, it is only a matter of time until we see a circulation of elites because the managerial regime is failing precisely at the moment of its apparent victory lap.</p> </blockquote> <p>This is what people call &ldquo;overtightening the screws&rdquo; in Russia. I&rsquo;m not sure it actually works, to be honest, but a man can hope.</p> <blockquote> <p>He claimed that the regime faces a ‘paradigm crisis’ in which ‘the gap between its democratic and liberal self-descriptions and its imposed social policies’ would become too obvious to escape notice and therefore ‘the efforts to justify these policies with archaic terminology or human rights rhetoric no longer elicit widespread belief.</p> </blockquote> <blockquote> <p>At Davos a few years ago, the Edelman survey showed us that the good news is the elite across the world trust each other more and more, so we can come together and design and do beautiful things together. The bad news is that in every single country they were polling, the majority of people trusted that elite less.</p> </blockquote> <p>A great read, highly recommend.</p> $4 Zigbee Lightbulb Deep Dive https://bubelov.com/blog/2025/ali-lightbulb/ Wed, 01 Oct 2025 00:00:00 +0000 https://bubelov.com/blog/2025/ali-lightbulb/ <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#intro">Intro</a></li> <li><a href="#node-descriptor">Node Descriptor</a> <ul> <li><a href="#logical-type">Logical Type</a></li> <li><a href="#complex-descriptor-available">Complex Descriptor Available</a></li> <li><a href="#user-descriptor-available">User Descriptor Available</a></li> <li><a href="#reserved">Reserved</a></li> <li><a href="#aps-flags">APS Flags</a></li> <li><a href="#frequency-band">Frequency Band</a></li> <li><a href="#medium-access-control-capability-flags">Medium Access Control Capability Flags</a></li> <li><a href="#manufacturer-code">Manufacturer Code</a></li> <li><a href="#maximum-buffer-size">Maximum Buffer Size</a></li> <li><a href="#maximum-incoming-transfer-size">Maximum Incoming Transfer Size</a></li> <li><a href="#server-mask">Server Mask</a></li> <li><a href="#maximum-outgoing-transfer-size">Maximum Outgoing Transfer Size</a></li> <li><a href="#descriptor-capability-field">Descriptor Capability Field</a></li> </ul> </li> <li><a href="#endpoint-1-of-1">Endpoint 1 of 1</a> <ul> <li><a href="#profile-id">Profile ID</a></li> <li><a href="#device-type">Device Type</a></li> <li><a href="#input-clusters">Input Clusters</a></li> <li><a href="#output-clusters">Output Clusters</a></li> </ul> </li> <li><a href="#manufacturer">Manufacturer</a></li> <li><a href="#model">Model</a></li> <li><a href="#class">Class</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="intro">Intro</h2> <p>So, my first Zigbee bulbs were a bunch of Philips Hues. I snagged a pretty good deal on the bundle, they were still pricey even with the discount. I was expecting top-notch quality, but two of them kicked the bucket in under two years, which got me looking for other options.</p> <p>The first (and only) alternative I&rsquo;ve tried is this <code>eWeLink</code> model <code>CK-BL702-AL-01(7009_Z102LG03-1)</code>. Yeah, the name sounds kinda scary, but they were only four bucks each on AliExpress. I ordered six of them to swap out the old Philips WiZ WiFi downlights in my kitchen.</p> <p>The lights showed up surprisingly fast and they were all working perfectly. Pairing them with Home Assistant was a breeze, they just worked with the standard Zigbee 3.0 pairing, no funny business.</p> <p>Now, let&rsquo;s take a look at the Zigbee signature for these lights:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;node_descriptor&#34;</span><span class="p">:</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;logical_type&#34;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;complex_descriptor_available&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;user_descriptor_available&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;reserved&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;aps_flags&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;frequency_band&#34;</span><span class="p">:</span> <span class="mi">8</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;mac_capability_flags&#34;</span><span class="p">:</span> <span class="mi">142</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;manufacturer_code&#34;</span><span class="p">:</span> <span class="mi">4742</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;maximum_buffer_size&#34;</span><span class="p">:</span> <span class="mi">127</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;maximum_incoming_transfer_size&#34;</span><span class="p">:</span> <span class="mi">242</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;server_mask&#34;</span><span class="p">:</span> <span class="mi">11264</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;maximum_outgoing_transfer_size&#34;</span><span class="p">:</span> <span class="mi">242</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;descriptor_capability_field&#34;</span><span class="p">:</span> <span class="mi">0</span> </span></span><span class="line"><span class="cl"> <span class="p">},</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;endpoints&#34;</span><span class="p">:</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;1&#34;</span><span class="p">:</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;profile_id&#34;</span><span class="p">:</span> <span class="s2">&#34;0x0104&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;device_type&#34;</span><span class="p">:</span> <span class="s2">&#34;0x010d&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;input_clusters&#34;</span><span class="p">:</span> <span class="p">[</span> </span></span><span class="line"><span class="cl"> <span class="s2">&#34;0x0000&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="s2">&#34;0x0003&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="s2">&#34;0x0004&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="s2">&#34;0x0005&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="s2">&#34;0x0006&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="s2">&#34;0x0008&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="s2">&#34;0x0300&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="s2">&#34;0x1000&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="s2">&#34;0xef00&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="s2">&#34;0xfc11&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="s2">&#34;0xfc57&#34;</span> </span></span><span class="line"><span class="cl"> <span class="p">],</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;output_clusters&#34;</span><span class="p">:</span> <span class="p">[</span> </span></span><span class="line"><span class="cl"> <span class="s2">&#34;0x0019&#34;</span> </span></span><span class="line"><span class="cl"> <span class="p">]</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">},</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;manufacturer&#34;</span><span class="p">:</span> <span class="s2">&#34;eWeLink&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;model&#34;</span><span class="p">:</span> <span class="s2">&#34;CK-BL702-AL-01(7009_Z102LG03-1)&#34;</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nt">&#34;class&#34;</span><span class="p">:</span> <span class="s2">&#34;zigpy.device.Device&#34;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><h2 id="node-descriptor">Node Descriptor</h2> <h3 id="logical-type">Logical Type</h3> <p>Logical Type set to <code>1</code> means these lights are Zigbee Routers, which is great news. Routers help extend your network&rsquo;s range and make it more reliable.</p> <p>There is a debate about lightbulbs though, since those can be turned off with a physical switch, potentially disturbing the network. I would prefer downlights being leaf nodes.</p> <h3 id="complex-descriptor-available">Complex Descriptor Available</h3> <p>This device doesn&rsquo;t have a complex descriptor, which is totally normal. Most devices skip this.</p> <h3 id="user-descriptor-available">User Descriptor Available</h3> <p>This is different from the complex descriptor, it&rsquo;s meant for storing a custom label on the device itself. It&rsquo;s empty here, which is standard if you&rsquo;ve never set one. I don&rsquo;t think Home Assistant even lets you edit this field right now anyway.</p> <p>HA&rsquo;s own labeling system is way more flexible, since it&rsquo;s not limited to a measly 16 characters like this Zigbee field.</p> <h3 id="reserved">Reserved</h3> <p>This is just an unused field saved for future features, so <code>0</code> is exactly what we&rsquo;d expect.</p> <h3 id="aps-flags">APS Flags</h3> <p>The Application Support Sub-layer (APS) flags are all zeros, which is pretty common.</p> <h3 id="frequency-band">Frequency Band</h3> <p>Value <code>8</code> means it&rsquo;s using the <code>2.4 GHz</code> band, the same crowded-but-universal frequency everything else uses.</p> <h3 id="medium-access-control-capability-flags">Medium Access Control Capability Flags</h3> <p><code>142</code> is <code>1000 1110</code> in binary (LSB <code>0111 0001</code>):</p> <table> <thead> <tr> <th>Bit</th> <th>Name</th> <th>Value</th> <th>Note</th> </tr> </thead> <tbody> <tr> <td>0</td> <td>Alternate PAN coordinator</td> <td>0</td> <td>Can&rsquo;t become a coordinator</td> </tr> <tr> <td>1</td> <td>Device type</td> <td>1</td> <td>Full function, can route</td> </tr> <tr> <td>2</td> <td>Power source</td> <td>1</td> <td>Mains-powered (not battery)</td> </tr> <tr> <td>3</td> <td>Receiver ON when idle</td> <td>1</td> <td>Always awake and listening</td> </tr> <tr> <td>4</td> <td>Reserved</td> <td>0</td> <td>Reserved</td> </tr> <tr> <td>5</td> <td>Reserved</td> <td>0</td> <td>Reserved</td> </tr> <tr> <td>6</td> <td>Security capability</td> <td>0</td> <td>Doesn&rsquo;t handle security at this level</td> </tr> <tr> <td>7</td> <td>Allocate address</td> <td>1</td> <td>Can help joining new devices</td> </tr> </tbody> </table> <p>This looks&hellip; interesting. The security capability bit being zero seems suspicious, but this is my first time digging this deep into a Zigbee descriptor. Maybe Zigbee 3.0 handles security at a different level and this flag is just legacy stuff?</p> <h3 id="manufacturer-code">Manufacturer Code</h3> <p><code>4742</code> appears to be Signify Netherlands B.V. (the company behind Philips Hue). This is pretty clever, these bulbs are basically pretending to be Hue bulbs to sneak into a Hue Bridge ecosystem.</p> <h3 id="maximum-buffer-size">Maximum Buffer Size</h3> <p><code>127</code> is the most common value and means it can handle the largest standard packet size.</p> <h3 id="maximum-incoming-transfer-size">Maximum Incoming Transfer Size</h3> <p><code>242</code> is weird here. To actually transfer chunks this big, the device would need to support packet fragmentation&hellip; but that capability isn&rsquo;t advertised in the APS flags. Something&rsquo;s not adding up.</p> <h3 id="server-mask">Server Mask</h3> <p>All the flags set here are &ldquo;reserved&rdquo;, meaning the device is using them for some non-standard, manufacturer-specific services. Not unusual for Zigbee devices trying to add (or pretending to have) extra features.</p> <h3 id="maximum-outgoing-transfer-size">Maximum Outgoing Transfer Size</h3> <p>Same issue as before, <code>242</code> suggests it should support large outgoing transfers, but the APS flags don&rsquo;t back that up. Another inconsistency.</p> <h3 id="descriptor-capability-field">Descriptor Capability Field</h3> <p><code>0</code> is standard, just confirms there are no extra descriptors beyond the basics.</p> <h2 id="endpoint-1-of-1">Endpoint 1 of 1</h2> <h3 id="profile-id">Profile ID</h3> <p><code>0x0104</code> is Zigbee Light Link (ZLL), the standard profile for smart lights.</p> <h3 id="device-type">Device Type</h3> <p><code>0x010D</code> means &ldquo;Extended Color Light.&rdquo; This is exactly what we&rsquo;d expect for a tunable white + RGB bulb, so Home Assistant should recognize it without any issues.</p> <h3 id="input-clusters">Input Clusters</h3> <table> <thead> <tr> <th>ID</th> <th>Note</th> </tr> </thead> <tbody> <tr> <td><code>0x0000</code></td> <td>Basic: Mandatory info like version and manufacturer.</td> </tr> <tr> <td><code>0x0003</code></td> <td>Identify: Makes the light blink, useful for finding it in a group.</td> </tr> <tr> <td><code>0x0004</code></td> <td>Groups: Lets you control multiple lights together.</td> </tr> <tr> <td><code>0x0005</code></td> <td>Scenes: Can save and recall preset colors/brightness.</td> </tr> <tr> <td><code>0x0006</code></td> <td>On/Off: The basics.</td> </tr> <tr> <td><code>0x0008</code></td> <td>Level Control: Dimming.</td> </tr> <tr> <td><code>0x0300</code></td> <td>Color Control: Handles all the colors and white temperatures.</td> </tr> <tr> <td><code>0x1000</code></td> <td>Touchlink Commissioning: Lets you pair by holding it close to a bridge.</td> </tr> <tr> <td><code>0xef00</code></td> <td>Manufacturer Specific: Who knows? Custom feature.</td> </tr> <tr> <td><code>0xfc11</code></td> <td>Manufacturer Specific: Another custom thing.</td> </tr> <tr> <td><code>0xfc57</code></td> <td>Manufacturer Specific: Could be for custom firmware updates.</td> </tr> </tbody> </table> <h3 id="output-clusters">Output Clusters</h3> <table> <thead> <tr> <th>ID</th> <th>Note</th> </tr> </thead> <tbody> <tr> <td><code>0x0019</code></td> <td>OTA Updates: Can receive firmware over-the-air.</td> </tr> </tbody> </table> <h2 id="manufacturer">Manufacturer</h2> <p>It&rsquo;s showing as <code>eWeLink</code>, but it&rsquo;s super easy to spoof this field when compiling firmware, so this doesn&rsquo;t necessarily mean it&rsquo;s officially from them.</p> <h2 id="model">Model</h2> <p><code>CK-BL702-AL-01(7009_Z102LG03-1)</code>, that&rsquo;s a model number alright.</p> <h2 id="class">Class</h2> <p><code>zigpy.device.Device</code> means Home Assistant is using a generic driver with no special quirks. It should just work without any fancy custom handling.</p> <h2 id="conclusion">Conclusion</h2> <p>Take this with a grain of salt, since it&rsquo;s the first Zigbee device I&rsquo;ve really dug into. But I&rsquo;ll be reviewing a lot more of these soon, I&rsquo;m going all-in on Zigbee and want to really understand how it works under the hood.</p> Road to Zigbee https://bubelov.com/blog/2025/zigbee/ Sun, 28 Sep 2025 00:00:00 +0000 https://bubelov.com/blog/2025/zigbee/ <p>I&rsquo;ve been using Home Assistant for a couple of years now. I got started, like most people, with some basic Philips lights. They have this cheap line called WiZ (like $15 a light), but you need a special app to set them up. I also tried their fancy Hue line (~$60 per light!) because I wanted to get into motion sensors and have lights that turn on automatically. The big difference is that WiZ uses WiFi, and Hue uses Zigbee.</p> <p>At first, I was kinda pissed that WiZ didn&rsquo;t have a motion sensor. But it turns out there&rsquo;s a legit reason, and it&rsquo;s not just the company being greedy. Motion sensors have to be super power-efficient, spending most of their time in a deep sleep to run for a year or more on a battery. WiFi is a total battery hog, so slapping it into a sensor would drain the thing in days. That&rsquo;s the real reason you don&rsquo;t see WiZ sensors.</p> <p>Home Assistant totally saved my wallet by letting me mix and match, I could use a Hue Zigbee sensor to trigger my cheap WiZ WiFi lights. But once I got a Zigbee router, I started looking into the protocol and realized it&rsquo;s way better than WiFi for smart home stuff, like in every way. The only thing holding me back from buying more Hue devices was their crazy price.</p> <p>Luckily, I found out AliExpress is flooded with no-name Zigbee devices, and I&rsquo;ve been buying them like crazy. The best part is that every new device makes my whole mesh network stronger and more reliable. You really can&rsquo;t beat the prices from China at the moment, it&rsquo;s a total game changer and there are no sane reasons to prefer WiFi.</p> LLMs and Abstraction Layers https://bubelov.com/blog/2025/llm-abstraction/ Fri, 06 Jun 2025 00:00:00 +0000 https://bubelov.com/blog/2025/llm-abstraction/ <p>One of the most divisive topics in programming is whether to use an ORM. Both sides have strong arguments, and each choice comes with significant trade-offs.</p> <p>If you choose an ORM, it can boost your productivity, but it may also slow down your code and introduce obscure, hard-to-debug issues. Writing raw SQL avoids an extra layer of abstraction, but it requires some SQL knowledge and often involves writing a lot of boilerplate code.</p> <p>Recently, I&rsquo;ve been experimenting with Deepseek and noticed that it excels at generating ORM boilerplate, which shifts the balance in the ORM debate. Now you can have both direct database access and high productivity.</p> <p>Systems with fewer abstraction layers are more maintainable, especially when removed abstractions are <a href="https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-abstractions/">leaky</a>.</p> <blockquote> <p>The law of leaky abstractions means that whenever somebody comes up with a wizzy new code-generation tool that is supposed to make us all ever-so-efficient, you hear a lot of people saying “learn how to do it manually first, then use the wizzy tool to save time.” Code generation tools which pretend to abstract out something, like all abstractions, leak, and the only way to deal with the leaks competently is to learn about how the abstractions work and what they are abstracting. So the abstractions save us time working, but they don’t save us time learning.</p> </blockquote> <p>Of course, you can apply the same argument to LLMs themselves, but they don&rsquo;t need to be present in codebases.</p> Bitcoin Release Unpacked - v29 https://bubelov.com/blog/2025/bitcoin-29-unpacked/ Wed, 23 Apr 2025 00:00:00 +0000 https://bubelov.com/blog/2025/bitcoin-29-unpacked/ <p><a href="https://bubelov.com/blog/2024/bitcoin-28-unpacked/">Previous post (v28)</a></p> <p>Every Bitcoin release is distributed as a single archive containing many cryptic files. In fact, you rarely need them all, but it&rsquo;s important to understand the purpose of each and every file in this archive in order to pick the ones you actually need.</p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#whats-changed-since-v28">What&rsquo;s Changed Since v28</a></li> <li><a href="#where-to-find-the-latest-release">Where to Find the Latest Release</a></li> <li><a href="#building-from-source-vs-getting-binaries">Building From Source vs Getting Binaries</a></li> <li><a href="#extracting-files-from-release-archive">Extracting Files From Release Archive</a></li> <li><a href="#bin-directory">/bin Directory</a></li> <li><a href="#bitcoinconf">/bitcoin.conf</a></li> <li><a href="#share-directory">/share Directory</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="whats-changed-since-v28">What&rsquo;s Changed Since v28</h2> <p>I didn&rsquo;t notice any changes in release bundle file names or structure. For code changes, you can check <a href="https://github.com/bitcoin/bitcoin/blob/master/doc/release-notes/release-notes-29.0.md#notable-changes">this page</a>.</p> <h2 id="where-to-find-the-latest-release">Where to Find the Latest Release</h2> <p>Bitcoin source code is currently <a href="https://github.com/bitcoin/bitcoin">hosted on GitHub</a>. When the devs feel that their code is stable enough to be used by node operators, they create a new release and bump its version number. GitHub has a <a href="https://github.com/bitcoin/bitcoin/releases">nice interface</a> which allows us to see the latest releases.</p> <p>The most recent release is tagged as <a href="https://github.com/bitcoin/bitcoin/releases/tag/v29.0">v29.0</a>, so that&rsquo;s the latest Bitcoin version, and that&rsquo;s what we&rsquo;ll download and examine in detail.</p> <h2 id="building-from-source-vs-getting-binaries">Building From Source vs Getting Binaries</h2> <p>Since Bitcoin is an open source project, you can fetch its source code and compile it yourself. However, this practice is discouraged because it&rsquo;s far more complicated compared with getting a pre-compiled package. Some might think that building from source is safer, but it couldn&rsquo;t be further from the truth. The archive with pre-built binaries is no less secure than the source code itself because it&rsquo;s protected by cryptographic signatures which you can verify if you suspect that your archive might have been tampered with.</p> <h2 id="extracting-files-from-release-archive">Extracting Files From Release Archive</h2> <p>Every Bitcoin release has a link to the <a href="https://bitcoincore.org/bin/bitcoin-core-29.0/">page</a> where you can download an archive with pre-built binaries. You&rsquo;ll see many archives there, but you only need the one which is built for your operating system and instruction set architecture. I&rsquo;m running Linux and I have an x86 CPU, so I need an <em>x86_64-linux-gnu</em> version:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">curl --output bitcoin-29.0.tar.gz https://bitcoincore.org/bin/bitcoin-core-29.0/bitcoin-29.0-x86_64-linux-gnu.tar.gz </span></span></code></pre></div><p>The name of the archive we just downloaded ends with <em>.tar.gz</em> which is supposed to give as a hint on how to deal with this file. It&rsquo;s common to assume that archives are always compressed, but you can create an uncompressed archive with a tool like <em>tar</em>. Since the compression is optional, it&rsquo;s a good practice to append the information about the compression method to the name of your archive. Bitcoin releases are always compressed with <em>gzip</em>, so that&rsquo;s the reason behind this weird file extension.</p> <p>Okay, now it&rsquo;s time to extract all the files from the archive we just downloaded:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">tar --extract --file bitcoin-29.0.tar.gz --verbose </span></span></code></pre></div><p>You should see the following output:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">bitcoin-29.0/ </span></span><span class="line"><span class="cl">bitcoin-29.0/README.md </span></span><span class="line"><span class="cl">bitcoin-29.0/bin/ </span></span><span class="line"><span class="cl">bitcoin-29.0/bin/bitcoin-cli </span></span><span class="line"><span class="cl">bitcoin-29.0/bin/bitcoin-qt </span></span><span class="line"><span class="cl">bitcoin-29.0/bin/bitcoin-tx </span></span><span class="line"><span class="cl">bitcoin-29.0/bin/bitcoin-util </span></span><span class="line"><span class="cl">bitcoin-29.0/bin/bitcoin-wallet </span></span><span class="line"><span class="cl">bitcoin-29.0/bin/bitcoind </span></span><span class="line"><span class="cl">bitcoin-29.0/bin/test_bitcoin </span></span><span class="line"><span class="cl">bitcoin-29.0/bitcoin.conf </span></span><span class="line"><span class="cl">bitcoin-29.0/share/ </span></span><span class="line"><span class="cl">bitcoin-29.0/share/man/ </span></span><span class="line"><span class="cl">bitcoin-29.0/share/man/man1/ </span></span><span class="line"><span class="cl">bitcoin-29.0/share/man/man1/bitcoin-cli.1 </span></span><span class="line"><span class="cl">bitcoin-29.0/share/man/man1/bitcoin-qt.1 </span></span><span class="line"><span class="cl">bitcoin-29.0/share/man/man1/bitcoin-tx.1 </span></span><span class="line"><span class="cl">bitcoin-29.0/share/man/man1/bitcoin-util.1 </span></span><span class="line"><span class="cl">bitcoin-29.0/share/man/man1/bitcoin-wallet.1 </span></span><span class="line"><span class="cl">bitcoin-29.0/share/man/man1/bitcoind.1 </span></span><span class="line"><span class="cl">bitcoin-29.0/share/rpcauth/ </span></span><span class="line"><span class="cl">bitcoin-29.0/share/rpcauth/README.md </span></span><span class="line"><span class="cl">bitcoin-29.0/share/rpcauth/rpcauth.py </span></span></code></pre></div><p>Wow, that&rsquo;s a lot of files and directories. Let&rsquo;s go through them one by one:</p> <h2 id="bin-directory">/bin Directory</h2> <p>First, let&rsquo;s check that the <em>/bin</em> directory also exists on our target machine. That&rsquo;s the path where we&rsquo;re expected to place our Bitcoin executables.</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">ls -l /bin </span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">lrwxrwxrwx 1 root root 7 Oct 18 21:01 /bin -&gt; usr/bin </span></span></code></pre></div><p>In many modern Linux distributions, both <em>/bin</em> and <em>/usr/bin</em> point to the same directory, so <em>/bin</em> is just an alias for <em>/usr/bin</em>. This sounds like the best place to keep your Bitcoin binaries. If you have any doubts, you can always check <a href="https://refspecs.linuxfoundation.org/FHS_3.0/fhs/ch04s04.html">Filesystem Hierarchy Standard</a>.</p> <p>But wait, what about <a href="https://refspecs.linuxfoundation.org/FHS_3.0/fhs/ch04s09.html">this entry</a>?</p> <blockquote> <p>Locally installed software must be placed within <em>/usr/local</em> rather than <em>/usr</em> unless it is being installed to replace or upgrade software in <em>/usr</em>.</p> </blockquote> <p>It turns out, the best place to install Bitcoin binaries is <em>/usr/local/bin</em>. Now that we know where to install those binaries, let&rsquo;s check what they actually do:</p> <table> <thead> <tr> <th>File</th> <th>Purpose</th> </tr> </thead> <tbody> <tr> <td>bitcoin-cli</td> <td>Command line tool which can talk to a running <em>bitcoind</em> or <em>bitcoin-qt</em> instance.</td> </tr> <tr> <td>bitcoind</td> <td>Bitcoin Core server, without GUI included.</td> </tr> <tr> <td>bitcoin-qt</td> <td>Qt-based GUI with a built-in Bitcoin Core server.</td> </tr> <tr> <td>bitcoin-tx</td> <td>A standalone tool which can help you manage raw transactions.</td> </tr> <tr> <td>bitcoin-util</td> <td>It can provide functionality that does not rely on the ability to access a running Bitcoin Server. The only available command so far is <em>grind</em> which takes a hex-encoded header and grinds its nonce until its <em>nBits</em> is satisfied.</td> </tr> <tr> <td>bitcoin-wallet</td> <td>A standalone tool which is used to manage Bitcoin Core wallet.dat files. Note that Bitcoin Core server (bitcoin-qt or bitcoind) can manage wallets by itself so this tool is completely optional.</td> </tr> <tr> <td>test_bitcoin</td> <td>The binary that implements all of Bitcoin Core&rsquo;s unit tests. It has been verified to pass all tests when this build of Bitcoin Core was created, but you can always repeat those tests if you feel like it.</td> </tr> </tbody> </table> <h2 id="bitcoinconf">/bitcoin.conf</h2> <p>Most programs can be configured to better fit a particular environment and Bitcoin Core is no exception. Older versions of Bitcoin Core didn&rsquo;t really have an easy way to see all the possible configuration parameters and their purpose, and that led to the introduction of the &ldquo;skeleton&rdquo; conf file. This file can be copied to your data directory, but it doesn&rsquo;t change any configuration options, by default. You&rsquo;re supposed to open this file, find the option you want to adjust, and uncomment the string which enables this option. This way, you can clearly see the default values for every possible configuration option, as well as any active overrides.</p> <h2 id="share-directory">/share Directory</h2> <p>The last directory included in our release is <em>/share</em>, so let&rsquo;s first figure out its purpose.</p> <blockquote> <p>Any program or package which contains or requires data that doesn&rsquo;t need to be modified should store that data in /usr/share (or /usr/local/share, if installed locally)</p> </blockquote> <p>Well, man pages aren&rsquo;t supposed to be edited, so it would make perfect sense to place them in <em>/usr/local/share</em> directory.</p> <table> <thead> <tr> <th>File</th> <th>Purpose</th> </tr> </thead> <tbody> <tr> <td>man/man1/bitcoin-cli.1</td> <td>man page for bitcoin-cli binary</td> </tr> <tr> <td>man/man1/bitcoind.1</td> <td>man page for bitcoind binary</td> </tr> <tr> <td>man/man1/bitcoin-qt.1</td> <td>man page for bitcoin-qt binary</td> </tr> <tr> <td>man/man1/bitcoin-tx.1</td> <td>man page for bitcoin-tx binary</td> </tr> <tr> <td>man/man1/bitcoin-util.1</td> <td>man page for bitcoin-util binary</td> </tr> <tr> <td>man/man1/bitcoin-wallet.1</td> <td>man page for bitcoin-wallet binary</td> </tr> </tbody> </table> <p>It&rsquo;s generally a good idea to install manuals for all the tools you&rsquo;re planning to use because they tend to be quite informative, and they don&rsquo;t need an Internet connection to be accessed.</p> <p>There is also a folder called <em>rpcauth</em>, and it contains a simple script used to generate JSON-RPC users.</p> <table> <thead> <tr> <th>File</th> <th>Purpose</th> </tr> </thead> <tbody> <tr> <td>rpcauth/README.md</td> <td>Explains how to use rpcauth.py</td> </tr> <tr> <td>rpcauth/rpcauth.py</td> <td>A script used to create JSON-RPC users</td> </tr> </tbody> </table> <h2 id="conclusion">Conclusion</h2> <p>Bitcoin releases tend to contain many files, so it might be hard to understand what&rsquo;s going on and which files are necessary for your particular use. For a casual desktop user, I&rsquo;d recommend installing only the following files:</p> <ul> <li>/bin/bitcoin-cli</li> <li>/bin/bitcoin-qt</li> <li>/share/man/man1/bitcoin-cli.1</li> <li>/share/man/man1/bitcoin-qt.1</li> </ul> <p>If you don&rsquo;t have or don&rsquo;t need a GUI and you want to run a headless node, this set of files might better fit your particular case:</p> <ul> <li>/bin/bitcoin-cli</li> <li>/bin/bitcoind</li> <li>/share/man/man1/bitcoin-cli.1</li> <li>/share/man/man1/bitcoind.1</li> </ul> BTC Map: March 2025 Update https://bubelov.com/blog/2025/btcmap-mar/ Tue, 01 Apr 2025 00:00:00 +0000 https://bubelov.com/blog/2025/btcmap-mar/ <p>Moved to <a href="https://blog.btcmap.org/posts/2025-03/">https://blog.btcmap.org/posts/2025-03/</a></p> Nuremberg: Supreme Court https://bubelov.com/blog/2025/nuremberg/ Sun, 30 Mar 2025 00:00:00 +0000 https://bubelov.com/blog/2025/nuremberg/ <p>ISBN: 978-5-04-165934-9</p> <p>Author: <a href="https://www.imdb.com/name/nm1494744/">Aleksandr Zvyagintsev</a></p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#the-state-of-nature-from-hobbes-to-globalism">The State of Nature: From Hobbes to Globalism</a></li> <li><a href="#operation-unthinkable-when-allies-become-enemies">Operation Unthinkable: When Allies Become Enemies</a></li> <li><a href="#alfred-rosenberg-the-nazi-architect-of-divide-and-rule">Alfred Rosenberg: The Nazi Architect of Divide and Rule</a></li> <li><a href="#greed-as-policy-the-nazi-betrayal-of-romania-and-finland">Greed as Policy: The Nazi Betrayal of Romania and Finland</a></li> <li><a href="#nazis-20-from-war-criminals-to-cold-war-assets">Nazis 2.0: From War Criminals to Cold War Assets</a></li> <li><a href="#conclusion-the-unmasking-of-power">Conclusion: The Unmasking of Power</a></li> </ul> </nav> </div> <h2 id="the-state-of-nature-from-hobbes-to-globalism">The State of Nature: From Hobbes to Globalism</h2> <p>What stops our neighbors from attacking us and stealing our possessions? This question troubled Thomas Hobbes, who rejected purely moral explanations. Instead, he proposed a practical, observable truth: Even the weakest person can kill the strongest through cunning or surprise. In this system, which Hobbes called the &ldquo;state of nature&rdquo;, everyone is both predator and prey. It&rsquo;s a bleak and chaotic existence.</p> <p>Can we escape this condition? Hobbes argued that we can do that by granting absolute power to a sovereign (a ruler or government) to enforce laws and prevent perpetual conflict. The trade-off? We sacrifice some freedoms but gain security.</p> <p>Yet this solution has a flaw: The state of nature never truly disappears. It merely shifts from individuals to sovereign states themselves. With no higher authority to restrain them, nations operate in a lawless arena where power alone dictates outcomes. The Nazis exploited this loophole, annexing territories until a stronger force stopped them.</p> <p>World War II spurred efforts to transcend this chaos through globalism, but the core dilemma remains. For international laws to work, states must surrender sovereignty, and a supreme enforcer must exist to punish violators. But no such enforcer exists. Even the Nuremberg Trials, while groundbreaking, were arguably victors&rsquo; justice disguised as legal precedent. They inspired hopes for a rules-based world order, but in reality, the state of nature endures.</p> <h2 id="operation-unthinkable-when-allies-become-enemies">Operation Unthinkable: When Allies Become Enemies</h2> <p>For the reasons I&rsquo;ve mentioned, I wasn&rsquo;t particularly interested in the Nuremberg Trials. I bought this book on impulse, mostly because of its impressive size and the author&rsquo;s reputation, but it turned out to be a great way to see a cohesive narrative rather than scattered facts and anecdotes.</p> <p>One of the most striking revelations was Operation Unthinkable, a perfect illustration of the state of nature hypothesis. Shockingly, the British government planned to betray their Soviet allies as early as July 1, 1945, barely two months after Germany&rsquo;s surrender. The plan even included 12 rearmed German divisions, trained and funded by the British, to fight alongside western forces against the USSR.</p> <p>Equally telling was Operation Pincer, a similar contingency plan. Together, these schemes prove a harsh truth: Sovereign states can never truly be friends. Trust is a luxury; the state of nature is a paranoid game where survival demands eternal vigilance.</p> <h2 id="alfred-rosenberg-the-nazi-architect-of-divide-and-rule">Alfred Rosenberg: The Nazi Architect of Divide and Rule</h2> <p>This book provides detailed profiles of each defendant, so there is no need to rehash summaries. Instead, I&rsquo;ll focus on Alfred Rosenberg, because I found his story the most striking one.</p> <p>Born in the Russian Empire and educated in Moscow, Rosenberg became one of the chief ideologues of Nazism. His most sinister role? Overseeing the political restructuring of the Soviet Union&rsquo;s corpse. His strategy was classic divide-and-conquer.</p> <p>Rosenberg planned to split the Slavic population into rival states:</p> <ul> <li>&ldquo;Ukraine&rdquo; and &ldquo;Muscovy&rdquo; (a deliberately diminished Russia).</li> <li>These puppet regimes would weaken each other through perpetual conflict, ensuring neither could challenge German dominance.</li> </ul> <p>This wasn&rsquo;t just administrative tinkering, it was the Nazi empire&rsquo;s blueprint: fracture conquered peoples, exploit their divisions, and rule eternally.</p> <h2 id="greed-as-policy-the-nazi-betrayal-of-romania-and-finland">Greed as Policy: The Nazi Betrayal of Romania and Finland</h2> <p>Nazi Germany didn&rsquo;t act alone, it relied on a network of allies, each sharing a similar authoritarian governance structure and offering strategic resources. But what&rsquo;s most revealing about these partnerships is the sheer greed and duplicity of the Nazi regime.</p> <p>Take Romania, Hitler&rsquo;s most important Eastern Front ally. The Nuremberg trials exposed Germany&rsquo;s internal documents, proving Hitler never intended to honor his promises to Romania. Instead, his long-term plan was outright subjugation. Romania was a tool - first for oil and troops, later for subjugation.</p> <p>The same pattern held with Finland. Despite pledges of support, Hitler saw the Finns as expendable. Once their usefulness waned, so did Germany&rsquo;s loyalty.</p> <p>This is the state of nature in action: no trust, only ruthless self-interest. Alliances are temporary and betrayal is inevitable.</p> <h2 id="nazis-20-from-war-criminals-to-cold-war-assets">Nazis 2.0: From War Criminals to Cold War Assets</h2> <p>Many Nazis evaded prosecution through various strategies. While top captured officials faced harsh treatement, even they gambled on the Cold War&rsquo;s outbreak. Their logic? Time was on their side. As World War II&rsquo;s allies turned against each other, these Nazis, already anti-communist and embittered by their failure to crush the USSR, saw the west as potential new partners.</p> <p>But the more systemic escape route was knowledge. Skilled Nazis such as scientists, spies, and bureaucrats traded their expertise and secrets for immunity. Their value was clear and their crimes didn&rsquo;t concern their new sovereigns.</p> <p>This isn&rsquo;t just history, it&rsquo;s a lesson in ruthless pragmatism. When power is at stake, morality becomes negotiable. Assume your enemies will weaponize every advantage - because they will.</p> <h2 id="conclusion-the-unmasking-of-power">Conclusion: The Unmasking of Power</h2> <p>States rarely reveal their true nature - their machinery is perpetually shrouded in myth and manufactured morality. The Nuremberg Trials tore back this veil, exposing shocking realities that would otherwise have remained hidden. Some dismiss these revelations as unique to Nazi Germany, but the trials laid bare a vast, amoral network of global power politics that defies any ethical standard.</p> <p>The bitter irony? The very nations presiding at Nuremberg were simultaneously scheming against each other, even as they prosecuted others for similar acts of realpolitik. The trials confirmed what history whispers but rarely shouts: &ldquo;Might makes right&rdquo; remains the law of nations.</p> <p>For those seeking to explore further, here are some linked works I found worth exploring:</p> <ul> <li><a href="https://en.wikipedia.org/wiki/Douglas_Kelley">22 Cells in Nuremberg (A psychological study of the Nazi defendants)</a></li> <li><a href="https://en.wikipedia.org/wiki/The_Myth_of_the_Twentieth_Century">The Myth of the Twentieth Century (Rosenberg’s ideological manifesto, exposing Nazi thought)</a></li> </ul> BTC Map February Recap: Cutting Costs, Switching to Vector Maps, and Tracking Global Bitcoin Adoption https://bubelov.com/blog/2025/btcmap-feb/ Mon, 03 Mar 2025 00:00:00 +0000 https://bubelov.com/blog/2025/btcmap-feb/ <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#dev-updates">Dev Updates</a></li> <li><a href="#admin-updates">Admin Updates</a></li> <li><a href="#trending-countries">Trending Countries</a></li> <li><a href="#trending-communities">Trending Communities</a></li> <li><a href="#most-active-editors">Most Active Editors</a></li> <li><a href="#global-metrics">Global Metrics</a></li> <li><a href="#new-lightning-adoption-report">New Lightning Adoption Report</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="dev-updates">Dev Updates</h2> <p>As a purely open source infrastructure project, BTC Map faces some significant monetization challenges. We have serious funding issues and most of the work is done by volunteers with no compensation at all, and we also need to pay for our infra, which means that we&rsquo;re always focused on two things:</p> <ul> <li>Cutting costs</li> <li>Finding sponsors</li> </ul> <p>While running a full node is fun, it costs ~$100 a month in VPS storage fees. To cut costs, we switched to a pruned node. It looks like the migration went well, and our CLN node has no issues running in a pruned setup.</p> <p>Another significant expense item is Stadia Maps subscription, it costs ~$300 a month, so we were able to migrate to a free alternative:</p> <p><a href="https://openfreemap.org/">https://openfreemap.org/</a></p> <p>Vector tiles look nicer, so I&rsquo;m very happy with this change. We also needed to switch to a self-hosted solution for OpenGraph static map image generation.</p> <p>Here is a list of other, less significant updates:</p> <ul> <li>RPC API underwent heavy refactoring, making it easier to use and maintain.</li> <li>Some database queries and struct mappings were improved.</li> <li>Element issues were promoted to a separate entity, which makes our API more composable. The <code>/elements</code> endpoint won&rsquo;t return the issues as a tag, now there is a separate <code>/element-issues</code> endpoint for that.</li> <li>Our web dashboard has been referenced in several Lightning adoption reports, prompting us to optimize its design and improve page load times.</li> </ul> <h2 id="admin-updates">Admin Updates</h2> <p>By onboarding additional community admins, we reduced the community submission backlog to just <a href="https://github.com/teambtcmap/btcmap-data/issues?q=is%3Aissue%20state%3Aopen%20label%3Acommunity-submission">14 open tickets</a>.</p> <h2 id="trending-countries">Trending Countries</h2> <ol> <li><a href="https://btcmap.org/country/br">Brazil</a> - 313 edits + 4 comments</li> <li><a href="https://btcmap.org/country/cr">Costa Rica</a> - 139 edits + 2 comments</li> <li><a href="https://btcmap.org/country/sv">El Salvador</a> - 108 changes + 5 comments</li> <li><a href="https://btcmap.org/country/za">South Africa</a> - 93 changes + 2 comments</li> <li><a href="https://btcmap.org/country/cz">Czechia</a> - 70 changes + 7 comments</li> </ol> <h2 id="trending-communities">Trending Communities</h2> <ol> <li><a href="https://btcmap.org/community/bitcoin-jungle">Bitcoin Jungle</a> - 67 edits + 2 comments</li> <li><a href="https://btcmap.org/community/floripacoin">FloripaCoin</a> - 37 edits</li> <li><a href="https://btcmap.org/community/nzbitcoiners">NZBitcoiners.org</a> - 29 edits + 5 comments</li> <li><a href="https://btcmap.org/community/bitcoin_berlin">Bitcoin Berlin - El Salvador</a> - 33 edits</li> <li><a href="https://btcmap.org/community/dezentralschweiz">Dezentralschweiz</a> - 29 edits + 3 comments</li> </ol> <h2 id="most-active-editors">Most Active Editors</h2> <ol> <li><a href="https://www.openstreetmap.org/user/Rockedf">Rockedf</a> - 297 edits</li> <li><a href="https://www.openstreetmap.org/user/descubrebitcoin">descubrebitcoin</a> - 184 edits</li> <li><a href="https://www.openstreetmap.org/user/Comino">Comino</a> - 102 edits</li> <li><a href="https://www.openstreetmap.org/user/saunter">saunter</a> - 95 edits</li> <li><a href="https://www.openstreetmap.org/user/empty_child">empty_child</a> - 71 edits</li> </ol> <h2 id="global-metrics">Global Metrics</h2> <p>The total number of merchants has increased from 14,096 to 14,563.</p> <p><figure> <a href="https://bubelov.com/blog/2025/btcmap-feb/total_hu_7d2a34f10cee2100.webp"> <img src="https://bubelov.com/blog/2025/btcmap-feb/total_hu_8f1c616bab30b5ae.webp" alt="" /> </a> <figcaption>Total number of merchants.</figcaption> </figure></p> <p>The number of recently verified (1y) merchants has increased from 7,851 to 7,879. We need more local maintainers if we want to bump those numbers.</p> <h2 id="new-lightning-adoption-report">New Lightning Adoption Report</h2> <p>We were mentioned in a latest <a href="https://breez.technology/documents/Report_BitcoinPayments_Breez_1A1z.pdf">Lightning adoption report</a> by Breez. This is an interesting read, and it looks like LN adoption has been accelerating in the last few months. Our data also confirms their observations.</p> <h2 id="conclusion">Conclusion</h2> <p>While funding is still an unsolved issue, we were able to cut our infra expenses and reduce the number of external dependencies. All the key trends are healthy, but we still need to onboard more editors and decentralize the editing process.</p> <p>You can support us by re-verifying <a href="https://btcmap.org/map?outdated">outdated locations</a> in your area. Every contribution makes Bitcoin ecosystem stronger!</p> BTC Map 2024: Lighting the Way for Bitcoin Adoption https://bubelov.com/blog/2025/btcmap-2024/ Sun, 12 Jan 2025 00:00:00 +0000 https://bubelov.com/blog/2025/btcmap-2024/ <p><a href="https://bubelov.com/blog/2024/btcmap-nov/">Previous post</a></p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#preface">Preface</a></li> <li><a href="#global-metrics-total-merchant-count">Global Metrics: Total Merchant Count</a></li> <li><a href="#global-metrics-up-to-date-merchant-count">Global Metrics: Up-to-date Merchant Count</a></li> <li><a href="#trending-countries">Trending Countries</a></li> <li><a href="#trending-communities">Trending Communities</a></li> <li><a href="#key-goals-for-2025">Key Goals for 2025</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="preface">Preface</h2> <p>2024 marked the second full year of BTC Map. The first attempts to build a map of Bitcoin merchants were observed more than a decade ago, but the first wave or merchant adoption faded away due to on-chain settlement limitations. Late 2022 was the year when Lightning started to shine and it felt like Bitcoin was ready for retail adoption. It turned out we weren&rsquo;t wrong about that! BTC Map continues to have a steady growth rates, powered by the mighty Lightning Network.</p> <h2 id="global-metrics-total-merchant-count">Global Metrics: Total Merchant Count</h2> <p>The total number of bitcoin accepting merchants is 40% higher than it was in the beginning of 2024. We started this year with 9,727 merchants on the map, and we ended with 13,673 merchants by the end of 2024.</p> <h2 id="global-metrics-up-to-date-merchant-count">Global Metrics: Up-to-date Merchant Count</h2> <p>We care deeply about data quality and so every merchant is supposed to be re-verified at least once a year. The number of freshly verified merchants has increased from 6,250 to 7,807, which is 25% higher than it was in the beginning of 2024. We aim to re-verify the other 5,866 merchants, but we can only do that if we get some help from the local communities. After all, the data is <em>theirs</em> not <em>ours</em>. The best way to help us is to <a href="https://btcmap.org/map?outdated">re-verify some outdated locations</a> in your area.</p> <h2 id="trending-countries">Trending Countries</h2> <ol> <li><a href="https://btcmap.org/country/br">Brazil</a> - 1,873 changes</li> <li><a href="https://btcmap.org/country/us">United States</a> - 1,745 changes</li> <li><a href="https://btcmap.org/country/cz">Czechia</a> - 1,644 changes</li> <li><a href="https://btcmap.org/country/it">Italy</a> - 1,567 changes</li> <li><a href="https://btcmap.org/country/sv">El Salvador</a> - 1,043 changes</li> <li><a href="https://btcmap.org/country/sv">Netherlands</a> - 985 changes</li> <li><a href="https://btcmap.org/country/za">South Africa</a> - 740 changes</li> <li><a href="https://btcmap.org/country/de">Germany</a> - 685 changes</li> <li><a href="https://btcmap.org/country/ge">Georgia</a> - 648 changes</li> <li><a href="https://btcmap.org/country/pt">Portugal</a> - 580 changes</li> </ol> <p>I want to give a huge thanks to <a href="https://www.openstreetmap.org/user/Rockedf">Rockedf</a> for consistently contributing to almost every region!</p> <h2 id="trending-communities">Trending Communities</h2> <ol> <li><a href="https://btcmap.org/community/jednadvacet-praha">Jednadvacet Praha</a> - 682 changes</li> <li><a href="https://btcmap.org/community/bitcoin-association-switzerland">Bitcoin Association Switzerland</a> - 566 changes</li> <li><a href="https://btcmap.org/community/mi-primer-bitcoin">Mi Primer Bitcoin</a> - 414 changes</li> <li><a href="https://btcmap.org/community/free-madeira">FREE Madeira</a> - 350 changes</li> <li><a href="https://btcmap.org/community/vancouver-bitcoiners">Vancouver Bitcoiners</a> - 319 changes</li> <li><a href="https://btcmap.org/community/bitcoin-bulgaria">Bitcoin Bulgaria</a> - 272 changes</li> <li><a href="https://btcmap.org/community/bitcoin-e-aqui">Bitcoin é aqui! Rolante/Riozinho-RS-BRASIL</a> - 261 changes</li> <li><a href="https://btcmap.org/community/arnhem-bitcoin-city">Arnhem Bitcoin City</a> - 258 changes</li> <li><a href="https://btcmap.org/community/porto-alegre-bitcoin">Porto Alegre Bitcoin</a> - 210 changes</li> <li><a href="https://btcmap.org/community/bitcoin-island-philippines">Bitcoin Island Philippines</a> - 205 changes</li> </ol> <h2 id="key-goals-for-2025">Key Goals for 2025</h2> <ul> <li>Re-verify more merchants (we need your help!).</li> <li>Finish community admin app and onboard more local admins (<a href="https://njump.me/npub1cn670f663n3ks02jnnlsvd5y88zjnefy8343ykaxs7y3nzzketrsrjwt8a">Nathan</a> is working on that).</li> <li>Clean up our community database, remove the stale records and make sure none of our communities are shitcoining (.we&rsquo;re hoping that the local admins will help us with that)</li> <li>Improve Android app by switching to vector tiles and fixing some UI and performance issues (I&rsquo;m working on that).</li> <li>Find someone to maintain our Web and iOS apps (if you&rsquo;re a dev, we need you help!).</li> <li>Automate merchant review process (I&rsquo;m working on that).</li> <li>Implement more lightweight web interface for the users who don&rsquo;t need need local caches and offline mode. It should be powered by a special API optimized for this use case (I&rsquo;ll probably work on that, but any help would be appreciated).</li> <li>Improve backend performance and fix some known bugs and bottlenecks.</li> <li>Visit more places and spend more sats on BTC Map =) (we&rsquo;ll keep doing that and we need your help!).</li> </ul> <h2 id="conclusion">Conclusion</h2> <p>I&rsquo;m satisfied with our performance in 2024, and I think the future looks bright. We&rsquo;re pleased to see more local communities spawning and the growing public awareness. Some popular Lightning wallets have also integrated BTC Map and we expect more to follow their lead. As Lightning Network continues to mature, it also gets much easier for merchants to accept sats. We expect a healthy growth rate in 2025!</p> BTC Map November Recap: Steady Growth, Challenges, and Community Contributions https://bubelov.com/blog/2024/btcmap-nov/ Sun, 01 Dec 2024 00:00:00 +0000 https://bubelov.com/blog/2024/btcmap-nov/ <p><a href="https://bubelov.com/blog/2024/btcmap-oct/">Previous post</a></p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#dev-updates-stability-observability-and-performance">Dev Updates: Stability, Observability, and Performance</a></li> <li><a href="#admin-updates-growing-areas-backlog">Admin Updates: Growing Areas Backlog</a></li> <li><a href="#trending-countries">Trending Countries</a></li> <li><a href="#trending-communities">Trending Communities</a></li> <li><a href="#global-metrics">Global Metrics</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="dev-updates-stability-observability-and-performance">Dev Updates: Stability, Observability, and Performance</h2> <p>November was a relatively quiet month, with a focus on addressing technical debt and resolving performance bottlenecks in the BTC Map backend. No other development activities related to BTC Map were reported.</p> <h2 id="admin-updates-growing-areas-backlog">Admin Updates: Growing Areas Backlog</h2> <p>Rockedf continues to manage our places backlog efficiently, with no tasks left unhandled. However, the areas backlog continues to grow due to limited admin activity.</p> <h2 id="trending-countries">Trending Countries</h2> <ol> <li><a href="https://btcmap.org/country/za">South Africa</a> - 410 changes</li> <li><a href="https://btcmap.org/country/br">Brazil</a> - 220 changes</li> <li><a href="https://btcmap.org/country/it">Italy</a> - 116 changes</li> <li><a href="https://btcmap.org/country/th">Thailand</a> - 107 changes</li> <li><a href="https://btcmap.org/country/ch">Switzerland</a> - 67 changes</li> </ol> <p>South Africa led the way with the most changes, primarily focused on data updates. Brazil added nearly 100 new places and re-verified many existing ones. Italy saw significant data quality improvements due to dedicated maintenance efforts. Thailand&rsquo;s changes were driven by a <a href="https://www.matichon.co.th/local/news_4918882">new local community</a>. Switzerland experienced a minor data quality improvement, mostly related to a local ATM network.</p> <p><figure> <a href="https://bubelov.com/blog/2024/btcmap-nov/merchant_hu_91494a34e76fcde1.webp"> <img src="https://bubelov.com/blog/2024/btcmap-nov/merchant_hu_a4896474ee961425.webp" alt="" /> </a> <figcaption>Bitcoin merchant in Thailand.</figcaption> </figure></p> <p>A big thanks to <a href="https://btcmap.org/tagger/7522075">Rockedf</a> for contributing to almost every region!</p> <h2 id="trending-communities">Trending Communities</h2> <ol> <li><a href="https://btcmap.org/community/bitcoin-amantikir">Bitcoin Amantikir</a> - 92 changes</li> <li><a href="https://btcmap.org/community/bitcoin-witsand">Bitcoin Witsand</a> - 79 changes</li> <li><a href="https://btcmap.org/community/bitcoin-cape-town-monthly-meetup">Bitcoin Cape Town Monthly Meetup</a> - 50 changes</li> <li><a href="https://btcmap.org/community/bitshack">Bitshack</a> - 35 changes</li> <li><a href="https://btcmap.org/community/free-madeira">FREE Madeira</a> - 33 changes</li> </ol> <p>Bitcoin Amantikir achieved a flawless 100% up-to-date rating through dedicated efforts. Bitcoin Witsand maintained its high rating, solidifying its position as one of Africa&rsquo;s top Bitcoin hubs.</p> <h2 id="global-metrics">Global Metrics</h2> <ul> <li>Verified merchants increased by 1.3% to 7,055, continuing the upward trend.</li> <li>Total merchants grew by 3.2% to 12,689, double the long-term trend.</li> <li>The average verification age increased by 4% to 357 days, slightly reducing data reliability.</li> </ul> <p>New merchant additions are at an all-time high, but maintaining up-to-date records remains a challenge. The main bottleneck continues to be the lack of local maintainers.</p> <h2 id="conclusion">Conclusion</h2> <p>Overall, progress is positive, but we need to address the following key issues:</p> <ul> <li>Many locations are outdated, and we need additional maintainers to verify merchant status and Bitcoin acceptance.</li> <li>We aim to highlight top-performing merchants to improve discoverability. Comments are currently unavailable on our iOS app and website due to a lack of active developers. We welcome contributions from anyone with relevant skills.</li> </ul> <p>The best way to help is by re-verifying <a href="https://btcmap.org/map?outdated">outdated locations</a> in your area.</p> BTC Map Monthly Recap - October 2024 https://bubelov.com/blog/2024/btcmap-oct/ Fri, 01 Nov 2024 00:00:00 +0000 https://bubelov.com/blog/2024/btcmap-oct/ <p><a href="https://bubelov.com/blog/2024/btcmap-sep/">Previous post</a></p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#merchant-comments">Merchant Comments</a></li> <li><a href="#vector-maps">Vector Maps</a></li> <li><a href="#trending-countries">Trending Countries</a></li> <li><a href="#trending-communities">Trending Communities</a></li> <li><a href="#global-metrics">Global Metrics</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="merchant-comments">Merchant Comments</h2> <p>I released merchant comments API last month, and I was busy integrating it in our Android app. Here are my key observations:</p> <ul> <li>Comments are a solid quality indicator, so I decided to show the number of comments on map pins, making it super easy to discover the highest quality merchants</li> <li>Comments are extremely valuable, they often contain useful tips and warnings, and they also mention discounts and promotions</li> <li>Comments are currently our best source of data on real user activity. Areas without comments are suspicious, so we can use comment data to identify fake or abandoned areas</li> </ul> <p><figure> <a href="https://bubelov.com/blog/2024/btcmap-oct/comments_hu_5960506c886753ce.webp"> <img src="https://bubelov.com/blog/2024/btcmap-oct/comments_hu_34d9b1a53f41c23b.webp" alt="" /> </a> <figcaption>Map markers with the number of comments</figcaption> </figure></p> <h2 id="vector-maps">Vector Maps</h2> <p>We had some good progress on switching to open source vector map dataset, powered by <a href="https://openfreemap.org/">OpenFreeMap</a>. After some initial discussions and tests, we decided to go ahead and roll out vector maps in the coming weeks. OpenFreeMap is a really cool project, our users can even self-host their own map data provider by following <a href="https://github.com/hyperknot/openfreemap/blob/main/docs/self_hosting.md">the official instructions</a>.</p> <h2 id="trending-countries">Trending Countries</h2> <ol> <li><a href="https://btcmap.org/country/br">Brazil</a> - 309 changes</li> <li><a href="https://btcmap.org/country/nl">Netherlands</a> - 202 changes</li> <li><a href="https://btcmap.org/country/it">Italy</a> - 154 changes</li> <li><a href="https://btcmap.org/country/es">Spain</a> - 130 changes</li> <li><a href="https://btcmap.org/country/sv">El Salvador</a> - 84 changes</li> <li><a href="https://btcmap.org/country/za">South Africa</a> - 80 changes</li> <li><a href="https://btcmap.org/country/ke">Kenya</a> - 77 changes</li> <li><a href="https://btcmap.org/country/sk">Slovakia</a> - 77 changes</li> <li><a href="https://btcmap.org/country/de">Germany</a> - 66 changes</li> <li><a href="https://btcmap.org/country/us">United States of America</a> - 61 changes</li> </ol> <p>Top 5 countries are well-known bitcoiner hubs, but South Africa and Kenya never showed up in our top trending countries list. Something might be brewing there, but only time will tell.</p> <p>Big thanks to <a href="https://btcmap.org/tagger/7522075">Rockedf</a> for contributing to almost every region!</p> <h2 id="trending-communities">Trending Communities</h2> <ol> <li><a href="https://btcmap.org/community/porto-alegre-bitcoin">Porto Alegre Bitcoin</a> - 89 changes</li> <li><a href="https://btcmap.org/community/bitcoin-dada-nairobi">Bitcoin Dada Nairobi</a> - 77 changes</li> <li><a href="https://btcmap.org/community/the-core">The Core</a> - 77 changes</li> <li><a href="https://btcmap.org/community/arnhem-bitcoin-city">Arnhem Bitcoin City</a> - 58 changes</li> <li><a href="https://btcmap.org/community/einundzwanzig-portugal">Einundzwanzig Portugal</a> - 56 changes</li> <li><a href="https://btcmap.org/community/einundzwanzig-schweiz">Einundzwanzig Schweiz</a> - 44 changes</li> <li><a href="https://btcmap.org/community/bitcoin-association-switzerland">Bitcoin Association Switzerland</a> - 44 changes</li> <li><a href="https://btcmap.org/community/free-madeira">Free Madeira</a> - 44 changes</li> <li><a href="https://btcmap.org/community/dezentralschweiz">Dezentralschweiz</a> - 44 changes</li> <li><a href="https://btcmap.org/community/de-bitcoin-meetup">De Bitcoin Meetup</a> - 41 changes</li> </ol> <p>Porto Alegre vent from 60% up-to-date to 100% up-to-date, thanks to <a href="https://btcmap.org/tagger/19288099">Go BTC</a> contributions.</p> <h2 id="global-metrics">Global Metrics</h2> <p>The number of verified merchants has increased from 6,747 to 6,935 (+2.8%), reversing previous month-on-month decline. It looks like we&rsquo;re able to keep ~7,000 merchants up-to-date, and we need more local maintainers if we want to bump those numbers.</p> <p>The total number of merchants has increased from 11,842 to 12,222 (+3.2%), which is ~x2 our long-term trend. The stream of new merchants is stronger than ever, but we&rsquo;re just struggling to keep some of them up-to-date.</p> <p>The average number of days since the last verification has increased from 340 to 344 (+1.2%), which means that our data is slightly less reliable than it was a month ago. This is yet another confirmation that our main bottleneck is the lack of local maintainers.</p> <h2 id="conclusion">Conclusion</h2> <p>Things are looking good overall, and I think that we need to focus on the following issues:</p> <ul> <li>There are many outdated locations, we need more maintainers who can contact those merchants and verify that they are still in business and still accept sats</li> <li>Some merchants are just better than others, we need to make the best ones more discoverable. Comments is a good first step but there are many other things we can do to highlight our rockstar merchants!</li> <li>Our iOS and Web apps need some love, it would be cool to onboard more devs who can fill the feature gap and fix some known bugs</li> </ul> <p>The best way to help us is to re-verify some outdated locations in your area, if you have any:</p> <p><a href="https://btcmap.org/map?outdated">https://btcmap.org/map?outdated</a></p> Bitcoin Release Unpacked - v28 https://bubelov.com/blog/2024/bitcoin-28-unpacked/ Mon, 07 Oct 2024 00:00:00 +0000 https://bubelov.com/blog/2024/bitcoin-28-unpacked/ <p><a href="https://bubelov.com/blog/2022/bitcoin-release/">Previous post (v27)</a></p> <p>Every Bitcoin release is distributed as a single archive containing many cryptic files. In fact, you rarely need them all, but it&rsquo;s important to understand the purpose of each and every file in this archive in order to pick the ones you actually need.</p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#whats-changed-since-v27">What&rsquo;s Changed Since v27</a></li> <li><a href="#where-to-find-the-latest-release">Where to Find the Latest Release</a></li> <li><a href="#building-from-source-vs-getting-binaries">Building From Source vs Getting Binaries</a></li> <li><a href="#extracting-files-from-release-archive">Extracting Files From Release Archive</a></li> <li><a href="#bin-directory">/bin Directory</a></li> <li><a href="#bitcoinconf">/bitcoin.conf</a></li> <li><a href="#share-directory">/share Directory</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="whats-changed-since-v27">What&rsquo;s Changed Since v27</h2> <p>It looks like <a href="https://github.com/bitcoin/bitcoin/pull/29189">libconsensus is out</a>, which means there are 4 less files and 2 less directories in v28. Here is a full list of removed files and dirs:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">/include/ </span></span><span class="line"><span class="cl">/include/bitcoinconsensus.h </span></span><span class="line"><span class="cl">/lib/ </span></span><span class="line"><span class="cl">/lib/libbitcoinconsensus.so </span></span><span class="line"><span class="cl">/lib/libbitcoinconsensus.so.0 </span></span><span class="line"><span class="cl">/lib/libbitcoinconsensus.so.0.0.0 </span></span></code></pre></div><h2 id="where-to-find-the-latest-release">Where to Find the Latest Release</h2> <p>Bitcoin source code is currently <a href="https://github.com/bitcoin/bitcoin">hosted on GitHub</a>. When the devs feel that their code is stable enough to be used by node operators, they create a new release and bump its version number. GitHub has a <a href="https://github.com/bitcoin/bitcoin/releases">nice interface</a> which allows us to see the latest releases.</p> <p>The most recent release is tagged as <a href="https://github.com/bitcoin/bitcoin/releases/tag/v28.0">v28.0</a>, so that&rsquo;s the latest Bitcoin version, and that&rsquo;s what we&rsquo;ll download and examine in detail.</p> <h2 id="building-from-source-vs-getting-binaries">Building From Source vs Getting Binaries</h2> <p>Since Bitcoin is an open source project, you can fetch its source code and compile it yourself. However, this practice is discouraged because it&rsquo;s far more complicated compared with getting a pre-compiled package. Some might think that building from source is safer, but it couldn&rsquo;t be further from the truth. The archive with pre-built binaries is no less secure than the source code itself because it&rsquo;s protected by cryptographic signatures which you can verify if you suspect that your archive might have been tampered with.</p> <h2 id="extracting-files-from-release-archive">Extracting Files From Release Archive</h2> <p>Every Bitcoin release has a link to the <a href="https://bitcoincore.org/bin/bitcoin-core-28.0/">page</a> where you can download an archive with pre-built binaries. You&rsquo;ll see many archives there, but you only need the one which is built for your operating system and instruction set architecture. I&rsquo;m running Linux and I have an x86 CPU, so I need an <em>x86_64-linux-gnu</em> version:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">curl --output bitcoin-28.0.tar.gz https://bitcoincore.org/bin/bitcoin-core-28.0/bitcoin-28.0-x86_64-linux-gnu.tar.gz </span></span></code></pre></div><p>The name of the archive we just downloaded ends with <em>.tar.gz</em> which is supposed to give as a hint on how to deal with this file. It&rsquo;s common to assume that archives are always compressed, but you can create an uncompressed archive with a tool like <em>tar</em>. Since the compression is optional, it&rsquo;s a good practice to append the information about the compression method to the name of your archive. Bitcoin releases are always compressed with <em>gzip</em>, so that&rsquo;s the reason behind this weird file extension.</p> <p>Okay, now it&rsquo;s time to extract all the files from the archive we just downloaded:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">tar --extract --file bitcoin-28.0.tar.gz --verbose </span></span></code></pre></div><p>You should see the following output:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">bitcoin-28.0/ </span></span><span class="line"><span class="cl">bitcoin-28.0/README.md </span></span><span class="line"><span class="cl">bitcoin-28.0/bin/ </span></span><span class="line"><span class="cl">bitcoin-28.0/bin/bitcoin-cli </span></span><span class="line"><span class="cl">bitcoin-28.0/bin/bitcoin-qt </span></span><span class="line"><span class="cl">bitcoin-28.0/bin/bitcoin-tx </span></span><span class="line"><span class="cl">bitcoin-28.0/bin/bitcoin-util </span></span><span class="line"><span class="cl">bitcoin-28.0/bin/bitcoin-wallet </span></span><span class="line"><span class="cl">bitcoin-28.0/bin/bitcoind </span></span><span class="line"><span class="cl">bitcoin-28.0/bin/test_bitcoin </span></span><span class="line"><span class="cl">bitcoin-28.0/bitcoin.conf </span></span><span class="line"><span class="cl">bitcoin-28.0/share/ </span></span><span class="line"><span class="cl">bitcoin-28.0/share/man/ </span></span><span class="line"><span class="cl">bitcoin-28.0/share/man/man1/ </span></span><span class="line"><span class="cl">bitcoin-28.0/share/man/man1/bitcoin-cli.1 </span></span><span class="line"><span class="cl">bitcoin-28.0/share/man/man1/bitcoin-qt.1 </span></span><span class="line"><span class="cl">bitcoin-28.0/share/man/man1/bitcoin-tx.1 </span></span><span class="line"><span class="cl">bitcoin-28.0/share/man/man1/bitcoin-util.1 </span></span><span class="line"><span class="cl">bitcoin-28.0/share/man/man1/bitcoin-wallet.1 </span></span><span class="line"><span class="cl">bitcoin-28.0/share/man/man1/bitcoind.1 </span></span><span class="line"><span class="cl">bitcoin-28.0/share/rpcauth/ </span></span><span class="line"><span class="cl">bitcoin-28.0/share/rpcauth/README.md </span></span><span class="line"><span class="cl">bitcoin-28.0/share/rpcauth/rpcauth.py </span></span></code></pre></div><p>Wow, that&rsquo;s a lot of files and directories. Let&rsquo;s go through them one by one:</p> <h2 id="bin-directory">/bin Directory</h2> <p>First, let&rsquo;s check that the <em>/bin</em> directory also exists on our target machine. That&rsquo;s the path where we&rsquo;re expected to place our Bitcoin executables.</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">ls -l /bin </span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">lrwxrwxrwx 1 root root 7 Oct 18 21:01 /bin -&gt; usr/bin </span></span></code></pre></div><p>In many modern Linux distributions, both <em>/bin</em> and <em>/usr/bin</em> point to the same directory, so <em>/bin</em> is just an alias for <em>/usr/bin</em>. This sounds like the best place to keep your Bitcoin binaries. If you have any doubts, you can always check <a href="https://refspecs.linuxfoundation.org/FHS_3.0/fhs/ch04s04.html">Filesystem Hierarchy Standard</a>.</p> <p>But wait, what about <a href="https://refspecs.linuxfoundation.org/FHS_3.0/fhs/ch04s09.html">this entry</a>?</p> <blockquote> <p>Locally installed software must be placed within <em>/usr/local</em> rather than <em>/usr</em> unless it is being installed to replace or upgrade software in <em>/usr</em>.</p> </blockquote> <p>It turns out, the best place to install Bitcoin binaries is <em>/usr/local/bin</em>. Now that we know where to install those binaries, let&rsquo;s check what they actually do:</p> <table> <thead> <tr> <th>File</th> <th>Purpose</th> </tr> </thead> <tbody> <tr> <td>bitcoin-cli</td> <td>Command line tool which can talk to a running <em>bitcoind</em> or <em>bitcoin-qt</em> instance.</td> </tr> <tr> <td>bitcoind</td> <td>Bitcoin Core server, without GUI included.</td> </tr> <tr> <td>bitcoin-qt</td> <td>Qt-based GUI with a built-in Bitcoin Core server.</td> </tr> <tr> <td>bitcoin-tx</td> <td>A standalone tool which can help you manage raw transactions.</td> </tr> <tr> <td>bitcoin-util</td> <td>It can provide functionality that does not rely on the ability to access a running Bitcoin Server. The only available command so far is <em>grind</em> which takes a hex-encoded header and grinds its nonce until its <em>nBits</em> is satisfied.</td> </tr> <tr> <td>bitcoin-wallet</td> <td>A standalone tool which is used to manage Bitcoin Core wallet.dat files. Note that Bitcoin Core server (bitcoin-qt or bitcoind) can manage wallets by itself so this tool is completely optional.</td> </tr> <tr> <td>test_bitcoin</td> <td>The binary that implements all of Bitcoin Core&rsquo;s unit tests. It has been verified to pass all tests when this build of Bitcoin Core was created, but you can always repeat those tests if you feel like it.</td> </tr> </tbody> </table> <h2 id="bitcoinconf">/bitcoin.conf</h2> <p>Most programs can be configured to better fit a particular environment and Bitcoin Core is no exception. Older versions of Bitcoin Core didn&rsquo;t really have an easy way to see all the possible configuration parameters and their purpose, and that led to the introduction of the &ldquo;skeleton&rdquo; conf file. This file can be copied to your data directory, but it doesn&rsquo;t change any configuration options, by default. You&rsquo;re supposed to open this file, find the option you want to adjust, and uncomment the string which enables this option. This way, you can clearly see the default values for every possible configuration option, as well as any active overrides.</p> <h2 id="share-directory">/share Directory</h2> <p>The last directory included in our release is <em>/share</em>, so let&rsquo;s first figure out its purpose.</p> <blockquote> <p>Any program or package which contains or requires data that doesn&rsquo;t need to be modified should store that data in /usr/share (or /usr/local/share, if installed locally)</p> </blockquote> <p>Well, man pages aren&rsquo;t supposed to be edited, so it would make perfect sense to place them in <em>/usr/local/share</em> directory.</p> <table> <thead> <tr> <th>File</th> <th>Purpose</th> </tr> </thead> <tbody> <tr> <td>man/man1/bitcoin-cli.1</td> <td>man page for bitcoin-cli binary</td> </tr> <tr> <td>man/man1/bitcoind.1</td> <td>man page for bitcoind binary</td> </tr> <tr> <td>man/man1/bitcoin-qt.1</td> <td>man page for bitcoin-qt binary</td> </tr> <tr> <td>man/man1/bitcoin-tx.1</td> <td>man page for bitcoin-tx binary</td> </tr> <tr> <td>man/man1/bitcoin-util.1</td> <td>man page for bitcoin-util binary</td> </tr> <tr> <td>man/man1/bitcoin-wallet.1</td> <td>man page for bitcoin-wallet binary</td> </tr> </tbody> </table> <p>It&rsquo;s generally a good idea to install manuals for all the tools you&rsquo;re planning to use because they tend to be quite informative, and they don&rsquo;t need an Internet connection to be accessed.</p> <p>There is also a folder called <em>rpcauth</em>, and it contains a simple script used to generate JSON-RPC users.</p> <table> <thead> <tr> <th>File</th> <th>Purpose</th> </tr> </thead> <tbody> <tr> <td>rpcauth/README.md</td> <td>Explains how to use rpcauth.py</td> </tr> <tr> <td>rpcauth/rpcauth.py</td> <td>A script used to create JSON-RPC users</td> </tr> </tbody> </table> <h2 id="conclusion">Conclusion</h2> <p>Bitcoin releases tend to contain many files, so it might be hard to understand what&rsquo;s going on and which files are necessary for your particular use. For a casual desktop user, I&rsquo;d recommend installing only the following files:</p> <ul> <li>/bin/bitcoin-cli</li> <li>/bin/bitcoin-qt</li> <li>/share/man/man1/bitcoin-cli.1</li> <li>/share/man/man1/bitcoin-qt.1</li> </ul> <p>If you don&rsquo;t have or don&rsquo;t need a GUI and you want to run a headless node, this set of files might better fit your particular case:</p> <ul> <li>/bin/bitcoin-cli</li> <li>/bin/bitcoind</li> <li>/share/man/man1/bitcoin-cli.1</li> <li>/share/man/man1/bitcoind.1</li> </ul> BTC Map Monthly Recap - September 2024 https://bubelov.com/blog/2024/btcmap-sep/ Tue, 01 Oct 2024 00:00:00 +0000 https://bubelov.com/blog/2024/btcmap-sep/ <p><a href="https://bubelov.com/blog/2024/btcmap-aug/">Previous post</a></p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#merchant-comments">Merchant Comments</a></li> <li><a href="#feed-reader-support">Feed Reader Support</a></li> <li><a href="#rpc-interface">RPC Interface</a></li> <li><a href="#trending-countries">Trending Countries</a></li> <li><a href="#trending-communities">Trending Communities</a></li> <li><a href="#global-metrics">Global Metrics</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="merchant-comments">Merchant Comments</h2> <p>I extracted about 900 valuable comments from our verification reports and I exposed them via BTC Map sync API. Comments turned out to be extremely valuable since they often add important context to certain Bitcoin merchants. The next steps are client app support and automating new comments.</p> <h2 id="feed-reader-support">Feed Reader Support</h2> <p>Some people are only interested in new places or comments in their areas, so they don&rsquo;t want to install our apps and check them all the time. BTC Map data is open, and we never intended to lock users in, that&rsquo;s why I created a few experimental Atom data feeds. Let&rsquo;s say you want to get notified of new places and comments in the Netherlands, so now you can just add the following feeds to your feed reader of choice:</p> <p><a href="https://api.btcmap.org/feeds/new-places/nl">https://api.btcmap.org/feeds/new-places/nl</a></p> <p><a href="https://api.btcmap.org/feeds/new-comments/nl">https://api.btcmap.org/feeds/new-comments/nl</a></p> <p>Of course, you can mix and match any number of BTC Map areas, communities and countries alike. Web feed links are always available on our website, in area activity section.</p> <h2 id="rpc-interface">RPC Interface</h2> <p>Most of our administrative tasks are now centralized, which means that processing new change requests might take a long time, depending on how busy we are. This model can&rsquo;t scale, that&rsquo;s why we&rsquo;re building an admin API, alongside GUI and CLI tools which can be used to simplify access to many local administrative tasks. There is no ETA on that, but I think we&rsquo;ll be able to delegate most tasks to local community managers pretty soon.</p> <h2 id="trending-countries">Trending Countries</h2> <ol> <li><a href="https://btcmap.org/country/nl">Netherlands</a> - 190 events and comments</li> <li><a href="https://btcmap.org/country/it">Italy</a> - 173 events and comments</li> <li><a href="https://btcmap.org/country/de">Germany</a> - 169 events and comments</li> <li><a href="https://btcmap.org/country/es">Spain</a> - 101 events and comments</li> <li><a href="https://btcmap.org/country/pt">Portugal</a> - 98 events and comments</li> <li><a href="https://btcmap.org/country/sv">El Salvador</a> - 95 events and comments</li> <li><a href="https://btcmap.org/country/gb">United Kingdom</a> - 83 events and comments</li> <li><a href="https://btcmap.org/country/br">Brazil</a> - 78 events and comments</li> <li><a href="https://btcmap.org/country/ch">Switzerland</a> - 77 events and comments</li> <li><a href="https://btcmap.org/country/cz">Czechia</a> - 74 events and comments</li> </ol> <p>Many merchants in the Netherlands were re-verified by <a href="https://www.openstreetmap.org/user/Comino">Comino</a>, which shows that a single maintainer can keep the whole country up to date. Having a single maintainer per country would allow us to keep the whole world up to date, so if your country has some old and outdated merchants, we need your help!</p> <p>Italian re-verification effort can mostly be attributed to <a href="https://www.openstreetmap.org/user/mpbin">mpbin</a>. This user not only re-verified many existing places but also added some missing general tags such as contact details and so on. Those tags are extremely valuable, so even if your area is up-to-date, you might want to check if you have enough contact details for every merchant. This account apparently belongs to <a href="https://bitcoinitalianetwork.com/">Bitcoin Italia Network</a>, and it looks like they&rsquo;re taking ownership over the whole region.</p> <p>Germany&rsquo;s data was enhanced by many different accounts, but 65% of German locations are still outdated, so this country needs more love from local editors. Spain continues to be in a good shape, thanks to <a href="https://www.openstreetmap.org/user/descubrebitcoin">descubrebitcoin</a> efforts, and Portugal data quality continues to improve, thanks to <a href="https://www.openstreetmap.org/user/Sxajne">Sxajne</a>.</p> <p>Big thanks to <a href="https://btcmap.org/tagger/7522075">Rockedf</a> for contributing to almost every region!</p> <h2 id="trending-communities">Trending Communities</h2> <ol> <li><a href="https://btcmap.org/community/satoshi-spritz">Satoshi Spritz</a> - 176 events and comments</li> <li><a href="https://btcmap.org/community/einundzwanzig-deutschland">Einundzwanzig Deutschlan</a> - 169 events and comments</li> <li><a href="https://btcmap.org/community/einundzwanzig-portugal">Einundzwanzig Portugal</a> - 98 events and comments</li> <li><a href="https://btcmap.org/community/einundzwanzig-schweiz">Einundzwanzig Schweiz</a> - 78 events and comments</li> <li><a href="https://btcmap.org/community/bitcoin-association-switzerland">Bitcoin Association Switzerland</a> - 77 events and comments</li> <li><a href="https://btcmap.org/community/dezentralschweiz">Dezentralschweiz</a> - 77 events and comments</li> <li><a href="https://btcmap.org/community/bitcoin-berlin">Bitcoin Berlin - El Salvador</a> - 60 events and comments</li> <li><a href="https://btcmap.org/community/free-madeira">Free Madeira</a> - 51 events and comments</li> <li><a href="https://btcmap.org/community/berlin-2140">Berlin 2140</a> - 48 events and comments</li> <li><a href="https://btcmap.org/community/einundzwanzig-berlin">Einundzwanzig Berlin</a> - 48 events and comments</li> </ol> <h2 id="global-metrics">Global Metrics</h2> <p>The number of verified merchants has dropped from 6,867 to 6,734 (-2%), which means that we&rsquo;re struggling to keep more than 5-7 thousands of places up-to-date, and we need more contributors who can re-verify outdated merchants.</p> <p>The total number of merchants has increased from 11,685 to 11,833 (+1.3%), which is in line with our long-term trend. The stream of new merchants has no signs of tapering, we&rsquo;re just struggling to maintain the old ones in certain regions.</p> <p>The average number of days since the last verification has increased from 323 to 340 (+5.3%), which means that our data is now significantly less reliable than it was a month ago. This is yet another confirmation that our main bottleneck is the lack of local maintainers.</p> <h2 id="conclusion">Conclusion</h2> <p>Our main issue is growing number of outdated locations, and getting more maintainers can take some time. It&rsquo;s not the only way to improve BTC Map though, so we can focus on highlighting the best quality merchants, which I&rsquo;m intending to be busy with during October. I have a few ideas on using different quality signals, and it might make sense to simply mark or hide outdated or unreliable merchants.</p> <p>The best way to help us is to re-verify some outdated locations in your area, if you have any:</p> <p><a href="https://btcmap.org/map?outdated">https://btcmap.org/map?outdated</a></p> No Canada: Why I Walked Away https://bubelov.com/blog/2024/canada/ Fri, 20 Sep 2024 00:00:00 +0000 https://bubelov.com/blog/2024/canada/ <p><a href="https://bubelov.com/blog/2022/canada/">Previous post</a></p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#merit-based-system-is-a-lie">Merit-Based System is a Lie</a></li> <li><a href="#exit-tax">Exit Tax</a></li> <li><a href="#high-cost-of-living-and-low-wages">High Cost of Living and Low Wages</a></li> <li><a href="#politics">Politics</a></li> <li><a href="#weather">Weather</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="merit-based-system-is-a-lie">Merit-Based System is a Lie</h2> <p>Canada&rsquo;s immigration is supposed to be based on merit, which sounds great on paper. In reality, the system can&rsquo;t tell real documents from fakes. That puts honest applicants in a tough spot. You either need to be ready to forge your papers, or you&rsquo;re up against tons of people from places where faking documents is pretty common.</p> <p>Sure, there are other paths, like using a diploma mill or going through a provincial program, but those are either shady or come with serious downsides. I looked into a few options myself, and none of them seemed worth it.</p> <h2 id="exit-tax">Exit Tax</h2> <p>Canada&rsquo;s basically a trap, easy to get into (especially if you&rsquo;re okay with faking stuff), but they&rsquo;ll bleed you dry if you try to leave. I had no idea until I met some Canadian expats here in Thailand. They told me I&rsquo;d be nuts to move there.</p> <p>That was a good lesson. If you want the real insights, talk to people who&rsquo;ve actually lived there their whole lives, not newcomers who are just trying to make their own expensive choice seem smart.</p> <h2 id="high-cost-of-living-and-low-wages">High Cost of Living and Low Wages</h2> <p>The only immigrants who praise Canada are the ones who came from real poverty back home. For them, $20 an hour feels like a dream. But Canada can&rsquo;t compete for skilled middle-class talent. Wages are low, taxes are painfully high, and real estate is barely affordable even for Canadians. An average tech job won’t get you a nice house in a nice neighborhood here, something that’s totally normal in plenty of other countries.</p> <h2 id="politics">Politics</h2> <p>Canada&rsquo;s in the U.S. sphere of influence and that&rsquo;s not bad in itself. What feels off is how one-sided it is. Canadians don&rsquo;t get easy access to the U.S., so they basically bear all the costs of that friendship without getting much in return. I&rsquo;ve talked to plenty of Canadians who&rsquo;d move south if they could, but being Canadian doesn&rsquo;t make it any easier.</p> <p>Also, I can&rsquo;t ignore that Canada’s funding the Nazi regime in Ukraine. Sending my tax money there would just feel wrong.</p> <h2 id="weather">Weather</h2> <p>I left Russia mostly because the weather is awful nine months out of the year. The thing is, Canada isn&rsquo;t much better, I can&rsquo;t imagine sitting through another long winter. Thailand&rsquo;s got its issues, but the weather here is just about perfect.</p> <h2 id="conclusion">Conclusion</h2> <p>Canada depends on immigrants, plain and simple. They have a strong incentive to market their programs. But like any marketing, the most important truths are the ones they leave out. That&rsquo;s why you have to do your own, careful, research.</p> <p>If you really want to know what it&rsquo;s like, don&rsquo;t just talk to newcomers, they&rsquo;re trying to justify their own decision. Talk to people who grew up there. That’s the fastest way to see the whole picture and make a choice that&rsquo;s right for you.</p> Scaling Bitcoin https://bubelov.com/blog/2024/scaling-bitcoin/ Thu, 05 Sep 2024 00:00:00 +0000 https://bubelov.com/blog/2024/scaling-bitcoin/ <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#introduction">Introduction</a></li> <li><a href="#scaling-is-secondary">Scaling is Secondary</a></li> <li><a href="#bitcoin-blockchain-is-for-savings">Bitcoin Blockchain is for Savings</a></li> <li><a href="#political-risks-of-savings-only-narrative">Political Risks of Savings-Only Narrative</a></li> <li><a href="#l2-beyond-savings">L2: Beyond Savings</a></li> <li><a href="#current-solutions">Current Solutions</a></li> <li><a href="#lightning-network">Lightning Network</a> <ul> <li><a href="#risks">Risks</a></li> <li><a href="#benefits">Benefits</a></li> <li><a href="#summary">Summary</a></li> </ul> </li> <li><a href="#liquid-network">Liquid Network</a> <ul> <li><a href="#risks-1">Risks</a></li> <li><a href="#benefits-trustless-swaps">Benefits: Trustless Swaps</a></li> <li><a href="#benefits-non-custodial-order-books">Benefits: Non-Custodial Order Books</a></li> <li><a href="#benefits-confidential-transactions">Benefits: Confidential Transactions</a></li> <li><a href="#liquid-federation">Liquid Federation</a></li> <li><a href="#aqua">AQUA</a></li> <li><a href="#liquid-assets">Liquid Assets</a></li> <li><a href="#summary-1">Summary</a></li> </ul> </li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="introduction">Introduction</h2> <p>When people discuss scaling Bitcoin, they are typically referring to increasing its transaction throughput beyond the current ~7 transactions per second. While the Lightning Network might not be a perfect solution, it is already available and can process an unlimited number of transactions. For this reason, I consider the throughput problem largely solved.</p> <p>While scaling for transactions is the primary goal, Bitcoin&rsquo;s utility can also expand far beyond storing and moving satoshis. Other blockchains, like Ethereum and Solana, have shown their ability to host assets such as stocks, bonds, and stablecoins. These use cases drive demand for their native tokens, demand that I would like to see redirected to Bitcoin.</p> <p>It&rsquo;s worth acknowledging that any use of Bitcoin beyond storing and moving satoshis is controversial, and many in the community are firmly opposed. However, I believe adding new use cases to the Bitcoin network is beneficial, provided they do not interfere with its primary function: storing and transferring value. For instance, while I support the idea of Bitcoin-powered assets, their transactions should not occur onchain where they could compete with normal payments.</p> <h2 id="scaling-is-secondary">Scaling is Secondary</h2> <p>Scaling a system that fails its core promises is dumb. Bitcoin&rsquo;s primary value proposition is its lack of a central authority. To ensure it remains truly decentralized, we must first solve critical challenges, most notably, the geographical and geopolitical diversification of mining and chip manufacturing. Progress on this front must take priority over less critical efforts.</p> <h2 id="bitcoin-blockchain-is-for-savings">Bitcoin Blockchain is for Savings</h2> <p>Bitcoin&rsquo;s inherent scarcity makes it undoubtedly suited for savings above all else. The blockchain itself was never designed for retail, as on-chain transactions are inherently slow and costly. These limitations are permanent, precluding the use of Bitcoin for day-to-day spending like fiat. Even onchain asset issuance, while possible, should be discouraged as it competes with the network&rsquo;s core financial transactions.</p> <h2 id="political-risks-of-savings-only-narrative">Political Risks of Savings-Only Narrative</h2> <p>While many current bitcoiners are content with a savings-only use case, I believe it is a mistake to ignore Bitcoin&rsquo;s other practical applications. We must remember that most people are not in a position to save significant amounts, and it is crucial to understand how Bitcoin is perceived outside of our own social bubbles. Bitcoin should be viewed as an instrument of freedom, not a manifesto of greed. If we remain fixated solely on savings, politicians will eventually convince the public that Bitcoin is useless for anything but &ldquo;hoarding&rdquo;. This narrative would grant them the public approval, whether silent or vocal, to launch a full-scale attack on the network.</p> <p>Onboarding as many people as possible is crucial for Bitcoin&rsquo;s long-term survival. Different people have different needs, and the number of individuals who require neutral, seizure-proof payment rails and financial instruments far exceeds the number of hardcore maximalists who only want to hold. To survive an inevitable confrontation with the extremely powerful entities that oppose these freedoms, Bitcoin must evolve to address this broader range of needs.</p> <h2 id="l2-beyond-savings">L2: Beyond Savings</h2> <p>We have already established that onchain scaling for Bitcoin is not feasible. This is why various spending and decentralized finance solutions position themselves as Layer 2s (L2s), they rely on separate systems built at a higher level of abstraction.</p> <p>The core concept is to shift activity off-chain while maintaining the security guarantees of the base layer. A fundamental rule for any true Bitcoin L2 is that it cannot create a new token, doing so would make it just another altcoin. If a system does not natively use bitcoin, it is not a Bitcoin scaling solution.</p> <h2 id="current-solutions">Current Solutions</h2> <p>Using bitcoin on a Layer 2 requires first locking the coins on the main chain to prevent double-spending. Examining the capital committed to major L2s reveals the scale of this activity:</p> <ul> <li>Lightning Network - 5,090 BTC (per <a href="https://1ml.com/">https://1ml.com/</a>)</li> <li>Liquid Network - 3,850 BTC (per <a href="https://liquid.network/">https://liquid.network/</a>)</li> </ul> <p>This total of under 9,000 BTC, representing a mere 0.05% of all bitcoin ever mined, is currently servicing the entire non-savings economy. This clearly demonstrates that retail bitcoin transactions are still a niche, albeit growing, market. The fact that such a small fraction of the total supply can facilitate this demand is a powerful insight.</p> <h2 id="lightning-network">Lightning Network</h2> <h3 id="risks">Risks</h3> <p>Because Lightning Network nodes must remain online to function, they are inherently hot wallets. Therefore, they should only be used for small sums, much like cash in a physical wallet, amounts you can afford to lose if a software vulnerability is ever exploited.</p> <h3 id="benefits">Benefits</h3> <p>The Lightning Network offers three key advantages:</p> <ol> <li>Low Cost: A single onchain transaction can enable hundreds of offchain payments, making microtransactions like buying a $5 coffee feasible.</li> <li>Speed: Payments are settled instantly.</li> <li>Privacy: Unlike onchain transactions, which are fully public, Lightning payments are not individually broadcast to the entire network, helping to obscure your financial activity.</li> </ol> <h3 id="summary">Summary</h3> <p>The Lightning Network is ideal for spenders, enabling unlimited, fast, and private transactions for a fraction of the cost of on-chain payments. However, due to its protocol design and the need for interactivity, Lightning is not well-suited for storing or transferring large amounts of capital.</p> <h2 id="liquid-network">Liquid Network</h2> <h3 id="risks-1">Risks</h3> <p>The Liquid Network is a custodial system because all user bitcoins are held by a federation via an 11-of-15 multisig wallet. This means it is an undeniable fact that your funds can be seized, and you relinquish control while using the network. Consequently, I believe Liquid should only be used for small amounts of money, where the utility of fast, cheap transactions might justify the significant custodial risk.</p> <p>This risk is inherently lower for regulated, KYC-based assets, as their legal structure offers investor protections. While I have no personal interest in such traditional assets, their existence within Liquid&rsquo;s framework makes sense.</p> <h3 id="benefits-trustless-swaps">Benefits: Trustless Swaps</h3> <blockquote> <p>A Liquid &ldquo;swap&rdquo; is a single transaction that finalizes a trustless exchange of assets between two or more parties. It is much safer than having to trust the other person to complete their part of a traditional two-step exchange after you have completed yours.</p> <p><a href="https://docs.liquid.net/docs/swaps-and-smart-contracts">https://docs.liquid.net/docs/swaps-and-smart-contracts</a></p> </blockquote> <blockquote> <p>A swap on Liquid is done using the Partially Signed Elements Transaction (PSET) standard, whereby the parties involved sign an input and output each and pass around the transaction until all inputs and outputs are accounted for and signed, whereupon the transaction become fully signed and valid for broadcast.</p> <p><a href="https://docs.liquid.net/docs/swaps-and-smart-contracts">https://docs.liquid.net/docs/swaps-and-smart-contracts</a></p> </blockquote> <p>A key feature of Liquid is the ability to perform non-custodial swaps between any assets on its network. While L-BTC is the foundational asset, the system also hosts tokenized stocks, bonds, and stablecoins. This enables peer-to-peer trading without the need for trust in an additional counterparty or a central exchange.</p> <h3 id="benefits-non-custodial-order-books">Benefits: Non-Custodial Order Books</h3> <p>Although Liquid enables peer-to-peer order matching, a service is still required for order discovery, a role often filled by a trusted third party like an exchange.</p> <p>This means an exchange interface is still needed, but its role is fundamentally different. Because these platforms facilitate non-custodial swaps, they cannot access user funds, eliminating the risk of fraud or collapse that plagues traditional custodial exchanges. This is a critical advancement.</p> <p>Going a step further, an open-source public order book could be developed, paving the way for minimal or even zero trading fees.</p> <h3 id="benefits-confidential-transactions">Benefits: Confidential Transactions</h3> <p>Although Bitcoin&rsquo;s base layer has transparent transactions, this doesn&rsquo;t preclude privacy at higher layers. Early developers considered confidential transactions but found the data overhead too great for the main chain. Liquid sidesteps this limitation by implementing confidential transactions by default on its Layer 2.</p> <p>This confidentiality extends beyond just amounts to also obscure the types of assets being transferred. As a result, you can receive a specific asset on Liquid with complete privacy, even from the governing federation.</p> <h3 id="liquid-federation">Liquid Federation</h3> <p>The Liquid Federation governs the network, and its most powerful members are the 15 &ldquo;functionaries.&rdquo; These entities control the multisig keys for all user bitcoins, act as miners, and require an 11-of-15 majority to sign blocks or release funds. The central security challenge is preventing this majority from colluding.</p> <p>The model&rsquo;s viability depends on a geographically and geopolitically diverse federation. Currently, this is not the case, significant overlap in VC funding and the concentration of members within the US sphere of influence creates a central point of failure.</p> <p>For the federation model to be truly sound, it must decentralize further, ideally by incorporating members from emerging sovereign nations. The theory is robust, but the current implementation has room for improvement.</p> <h3 id="aqua">AQUA</h3> <p>After using the Liquid-based AQUA wallet for several months, I have a largely positive impression. It stands out as the most feature-complete Liquid wallet, effectively demonstrating core functionalities like peg-ins/outs, confidential transactions, and asset swaps. Its ease of use is a major advantage. For Bitcoin merchants in particular, it could be a game-changer, as receiving payments on Liquid does not require managing inbound liquidity, a common hurdle on the Lightning Network.</p> <h3 id="liquid-assets">Liquid Assets</h3> <p>Liquid&rsquo;s ability to host multiple assets, including Bitcoin, makes it useful for a variety of purposes. For instance, despite my reservations about USDT, I find Liquid USDT to be the most practical and cost-effective way to send USD to Russia.</p> <p>The network also supports tokenized securities, such as MicroStrategy stocks and El Salvador bonds, enabling 24/7 peer-to-peer trading without brokers. This opens the door for many new financial products, albeit most will be subject to KYC regulations.</p> <h3 id="summary-1">Summary</h3> <p>While both Liquid and Lightning provide cheaper, faster transactions than the base chain, a key distinction is that Liquid assets can be held in cold storage, unlike funds in a Lightning channel. This advantage comes with a critical trade-off: reliance on the Liquid Federation. This makes Liquid a custodial system. Although it is more secure than a custodial Lightning wallet, the ever-present risk of the Federation acting maliciously limits its use to small transaction amounts.</p> <h2 id="conclusion">Conclusion</h2> <p>While both Liquid and Lightning enable faster, cheaper, and more private bitcoin transactions, along with the issuance of assets like stocks and bonds, each Layer 2 comes with its own trade-offs. My approach is to use both for small, everyday amounts, as I would not trust either with significant savings.</p> BTC Map Monthly Recap - August 2024 https://bubelov.com/blog/2024/btcmap-aug/ Sun, 01 Sep 2024 00:00:00 +0000 https://bubelov.com/blog/2024/btcmap-aug/ <p><a href="https://bubelov.com/blog/2024/btcmap-jul/">Previous post</a></p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#merchant-reviews">Merchant Reviews</a></li> <li><a href="#trending-countries">Trending Countries</a></li> <li><a href="#trending-communities">Trending Communities</a></li> <li><a href="#global-metrics">Global Metrics</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="merchant-reviews">Merchant Reviews</h2> <p>I bet most bitcoiners are aware of the concept of <a href="https://en.wikipedia.org/wiki/Revealed_preference">revealed preferences</a>, and we noticed that many BTC Map users are abusing our merchant verification form and leave place reviews instead of short notes on how they verified a merchant in question.</p> <p>Here is a couple of examples:</p> <blockquote> <p>How did you verify this?</p> <p>I went and got my haircut after hearing about the adoption that was happening here in Arnhem. There was the business owner and one of his employees. The employee didn&rsquo;t know that they accepted bitcoin but luckily since the owner was there - he assured him that they do. Mission success for a haircut and a shave.</p> </blockquote> <blockquote> <p>How did you verify this?</p> <p>The level of customer service of this restaurant is excellent 10/10</p> </blockquote> <p>We have hundreds of reviews like the ones above, and it&rsquo;s clear that many bitcoiners want to share their experience and add some context to certain locations. Since those reviews aren&rsquo;t properly organized, it will take some time to extract old reviews from our archives and make them public. As a next step, I think we should add a dedicated review button with a Lightning paywall in order to filter out spam reviews.</p> <h2 id="trending-countries">Trending Countries</h2> <ol> <li><a href="https://btcmap.org/country/br">Brazil</a> - 173 changes</li> <li><a href="https://btcmap.org/country/nl">Netherlands</a> - 101 changes</li> <li><a href="https://btcmap.org/country/it">Italy</a> - 84 changes</li> <li><a href="https://btcmap.org/country/au">Australia</a> - 67 changes</li> <li><a href="https://btcmap.org/country/cz">Czechia</a> - 52 changes</li> <li><a href="https://btcmap.org/country/de">Germany</a> - 50 changes</li> <li><a href="https://btcmap.org/country/pt">Portugal</a> - 45 changes</li> <li><a href="https://btcmap.org/country/sr">Suriname</a> - 42 changes</li> <li><a href="https://btcmap.org/country/us">United States of America</a> - 32 changes</li> <li><a href="https://btcmap.org/country/gb">United Kingdom</a> - 27 changes</li> </ol> <p>Brazil is keeping its lead in Bitcoin adoption with 36 new and 121 re-verified places. It has 1,062 Bitcoin merchants in total, and 729 of them have been recently verified.</p> <p>Netherlands is one of the first centers of Bitcoin adoption, with a famous <a href="http://www.arnhembitcoinstad.nl/">Arnhem Bitcoin City</a>, which have been boosting Bitcoin adoption since 2014. It looks like August was a busy month in the Netherlands, and now it has 50 more Bitcoin merchants.</p> <p>Italy is a big country with many Bitcoin-accepting merchants, but most of them are outdated, so we can&rsquo;t be certain that all the data is reliable. Italian bitcoiners are working on solving this issue, and they were able to re-verify 71 merchant in August.</p> <p>Big thanks to <a href="https://btcmap.org/tagger/7522075">Rockedf</a> for contributing to almost every region!</p> <h2 id="trending-communities">Trending Communities</h2> <ol> <li><a href="https://btcmap.org/community/bitcoin-island-philippines">Bitcoin Island Philippines</a> - 97 changes</li> <li><a href="https://btcmap.org/community/satoshi-spritz">Satoshi Spritz</a> - 87 changes</li> <li><a href="https://btcmap.org/community/bitcoin-e-aqui">Bitcoin é aqui! Rolante/Riozinho-RS-BRASIL</a> - 81 changes</li> <li><a href="https://btcmap.org/community/einundzwanzig-deutschland">Einundzwanzig Deutschland</a> - 51 changes</li> <li><a href="https://btcmap.org/community/einundzwanzig-portugal">Einundzwanzig Portugal</a> - 45 changes</li> <li><a href="https://btcmap.org/community/free-madeira">Free Madeira</a> - 29 changes</li> <li><a href="https://btcmap.org/community/einundzwanzig-schweiz">Einundzwanzig Schweiz</a> - 21 changes</li> <li><a href="https://btcmap.org/community/dezentralschweiz">Dezentralschweiz</a> - 21 changes</li> <li><a href="https://btcmap.org/community/bitcoin-association-switzerland">Bitcoin Association Switzerland</a> - 21 changes</li> <li><a href="https://btcmap.org/community/bitcoin-sydney">Bitcoin Sydney</a> - 17 changes</li> </ol> <p><a href="https://btcmap.org/community/bitcoin-island-philippines">Bitcoin Island Philippines</a> has many outdated locations, and most of them no longer accept bitcoins. Removing outdated merchants is very important, thanks <a href="https://btcmap.org/tagger/7612177">ctboss</a> for cleaning this region in August!</p> <p>As you can see, we only have 10 communities with 17 or more changes during the last 30 days, which is concerning. We clearly need to find more ways to reach out to unmaintained communities and encourage them to keep their data up to date and onboard more merchants.</p> <h2 id="global-metrics">Global Metrics</h2> <p>The number of verified merchants has dropped from 6,900 to 6,879 (-0.3%), which means that we need more contributors if we want all the regions to stay up-to-date. We&rsquo;re talking about ~20 additional verifications a month, which can be done even by a single dedicated contributor.</p> <p>The total number of merchants has increased from 11,532 to 11,687 (+1.3%), which is in line with our long-term trend. The stream of new merchants has no signs of tapering, we&rsquo;re just struggling to maintain the old ones in certain regions.</p> <p>The average number of days since the last verification has increased from 309 to 322 (+4.2%), which means that our data is now significantly less reliable than it was a month ago. This is yet another confirmation that our main bottleneck is the lack of local maintainers.</p> <h2 id="conclusion">Conclusion</h2> <p>Merchant adoption has no signs of slowing down, and we&rsquo;re working on providing BTC Map users with all the relevant tools to discover and maintain their local Bitcoin merchant data. I believe that collecting and displaying place reviews can help us highlight the best places and give users more confidence in our data set.</p> <p>The best way to help us is to re-verify some outdated locations in your area, if you have any:</p> <p><a href="https://btcmap.org/map?outdated">https://btcmap.org/map?outdated</a></p> Statism and Anarchy https://bubelov.com/blog/2024/statism-and-anarchy/ Fri, 23 Aug 2024 00:00:00 +0000 https://bubelov.com/blog/2024/statism-and-anarchy/ <p>Mikhail Bakunin&rsquo;s essay &ldquo;Statism and Anarchy&rdquo; outlines his critique of the state and his argument for social revolution as the path to improving the human condition.</p> <p>I believe that our political views are inseparable from our personal background, so let&rsquo;s review a quick summary of Bakunin&rsquo;s life and work.</p> <p>Mikhail Bakunin was born into a noble Russian family and became obsessed with German philosophy in his 20s. While studying abroad, he was radicalized by some of the prominent thinkers of the time. The Russian Empire, like other European powers, tolerated no dissent, forcing Bakunin to remain abroad to continue his revolutionary activities.</p> <p>Although European states were repressive, their greater sovereignty and significant interstate rivalries led them to sometimes turn a blind eye to certain revolutionaries. This was contingent on the belief that these individuals would harm their rivals more than themselves. Many radical thinkers used this &ldquo;jurisdictional arbitrage&rdquo; to find temporary refuge and continue their work of stirring social revolution.</p> <p>&ldquo;Statism and Anarchy&rdquo; analyzes the political trends of 19th-century Europe, with a specific focus on Germany. The essay also makes several broad statements that clarify the author&rsquo;s position on the nature of the state, such as:</p> <blockquote> <p>States, essentially opposed to each other and irreconcilable to the end, could not and cannot find any other basis for unification than in the friendly enslavement of the masses of the people, who constitute the common basis and goal of their existence.</p> </blockquote> <blockquote> <p>There is only one essential difference between a monarchy and the most democratic republic: in the former, the official world oppresses and robs the people for the greater benefit of the privileged, propertied classes, as well as their own pockets, in the name of the monarch; in a republic, however, it will oppress and rob the people in exactly the same way for the same pockets and classes, only now in the name of the people&rsquo;s will.</p> </blockquote> <p>In summary, Bakunin rejected all forms of government and was deeply skeptical of democracy as an alternative to traditional systems like monarchy. He warns that interstate cooperation is not the positive development it may appear to be. While it might end wars between nations, it would enable them to wage a far more devastating war against their own populations.</p> <p>I generally agree with Bakunin&rsquo;s view that the formal type of government is less important than the quality and character of the ruling class. It is evident that some countries are better managed than others, largely because they are more meritocratic and have better &ldquo;social ladders&rdquo;. However, this can be achieved through various systems. Democracy does not appear to be the most reliable path to this goal, and in this, I share his healthy skepticism.</p> <p>The essay also advances the theory that Slavic tribes were incapable of forming a state without Roman or Germanic influence. Bakunin points to the Polish and East Slavic Szlachta as an example of an imported oppressive class. It is important to note, however, that modern historians largely reject this view, which I also find difficult to accept. A related and popular narrative posits that the first Russian ruler, Rurik, was a foreigner invited to rule, a concept that similarly seems dubious and rooted in nationalist myth-making. Despite not being backed by any historical evidence, this story is occasionally repeated even by high-ranking officials like Putin himself, though their repetition of it should not be mistaken for genuine belief.</p> <p>Regarding another alternative to European monarchies, here is the author&rsquo;s opinion on giving power to a scientific class:</p> <blockquote> <p>A scientist is by his very nature inclined to all sorts of mental and moral depravity, and his chief vice is the exaltation of his knowledge, his own mind, and contempt for all who are ignorant. Give him control, and he will become the most intolerable tyrant, because learned pride is disgusting, offensive, and more oppressive than any other.</p> </blockquote> <p>It is clear that Bakunin believed scientists should stay away from power, as they are just as fallible as anyone else. He also articulated deeper reasons for this, which he formulated as follows:</p> <blockquote> <p>Thought precedes life - a recipe for centralization and dictatorship</p> </blockquote> <p>Bakunin and anarchists in general were not anti-science, but they warned that an over-reliance on abstraction can cause one to lose touch with life itself. They considered the top-down application of such abstractions and generalizations to be dangerous. Bakunin envisioned bottom-up societies built on a mutual sense of belonging through shared language and culture. For him, the main driver of social change was direct, on-the-ground experience, not abstract theories. He argued that one need not be a scientist, or even literate, to act in one&rsquo;s own best interest.</p> <p>It is also evident that the author held a strong animosity toward Germany. Consider the following quote as an example:</p> <blockquote> <p>Bismarck, with his usual boldness, his characteristic cynicism and contemptuous frankness, expressed in these words the whole essence of the political history of nations, the whole secret of state wisdom. The constant predominance and triumph of force - that is the real essence; everything that in political language is called law is only the sanctification of a fact created by force. Clearly, the masses of the people, thirsting for liberation, cannot expect it from the theoretical triumph of abstract law; they must conquer freedom by force, for which they must organize their elemental forces outside the state and against it.</p> </blockquote> <p>Here, Bakunin again criticizes Germany, portraying its leaders as a primary source of statist evil and centralization. He argued that Germans, unlike other Europeans, were averse to social revolution because of a cultural tendency to venerate state power. While I refrain from endorsing this sweeping generalization, it is a historical fact that Germany produced the Nazi regime and today operates as a key ally of the United States. This lends a degree of plausibility to the broader argument that certain cultural traditions, such as Confucianism in China, may place a higher value on obedience and social order.</p> <p>In essence, the essay serves as both an attack on German culture and a broader critique of state power and centralization. The following quote illustrates this:</p> <blockquote> <p>If there is a state, then there is necessarily domination, and therefore slavery; a state without slavery, open or disguised, is unthinkable - that is why we are enemies of the state.</p> </blockquote> <p>Couldn&rsquo;t agree more. Bakunin was a man of principle who split with the Marxists because he didn&rsquo;t believe building new states would fix the problems of the old ones. The Marxists wanted all power centralized in their hands. Given that Marxist states generally didn&rsquo;t turn out any better than monarchies or democracies, it looks like Bakunin&rsquo;s skepticism was right.</p> <h2 id="conclusion">Conclusion</h2> <p>Statism and Anarchy is a fascinating essay for anyone interested in the principal political and social trends of 19th-century Europe. While the book is not unbiased and, to my taste, contains excessive criticism of Germany, it serves as an excellent reference point for understanding the period.</p> <p>Bakunin emphasizes the strategic importance of targeting the most vulnerable parts of the state machinery. Given that our resources are limited, he argues we must avoid spreading them too thin. I find this logic compelling. In the modern context, the Achilles&rsquo; heel of the state is arguably its money printer. The ability to undermine central banking and national currencies could precipitate the collapse of the modern, hyper-centralized state. From this perspective, innovations like <a href="https://bubelov.com/tags/bitcoin">Bitcoin</a> offer an effective way of challenging the system.</p> <p>After reading this essay, I am motivated to explore the following topics:</p> <ul> <li>The history of the International Workingmen&rsquo;s Association.</li> <li>More on what caused the split between Marxists and Anarchists.</li> <li>The source of Hegel&rsquo;s profound influence. His work seemed unremarkable in my prior studies of philosophy, warranting a deeper dive into his ideas and their historical impact.</li> </ul> BTC Map Monthly Recap - July 2024 https://bubelov.com/blog/2024/btcmap-jul/ Tue, 13 Aug 2024 00:00:00 +0000 https://bubelov.com/blog/2024/btcmap-jul/ <p><a href="https://bubelov.com/blog/2024/btcmap-weekly-05-19/">Previous post</a></p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#events--areas">Events + Areas</a></li> <li><a href="#trending-countries">Trending Countries</a></li> <li><a href="#trending-communities">Trending Communities</a></li> <li><a href="#global-metrics">Global Metrics</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <p>I didn&rsquo;t post for a while due to an <a href="https://bubelov.com/blog/2024/russia/">unexpected trip to Russia</a>, but now I&rsquo;m back in Phuket and here are my thoughts on BTC Map progress in July.</p> <h2 id="events--areas">Events + Areas</h2> <p>There are a few important events which can happen to a BTC Map merchant, namely:</p> <ul> <li>Creation</li> <li>Edit</li> <li>Boost</li> <li>Deletion</li> </ul> <p>That&rsquo;s the basic lifecycle of any merchant. Unfortunately, it wasn&rsquo;t possible to associate a specific event with a specific BTC Map area, which limited our analytics. I was busy with removing this limitation during the last couple of weeks, and now we can easily rank different areas such as countries and communities by the number of important events. I think it&rsquo;s the best way to find trending areas for a specific time period.</p> <p>So, let&rsquo;s use this new capability to see what was trending in July:</p> <h2 id="trending-countries">Trending Countries</h2> <ol> <li><a href="https://btcmap.org/country/it">Italy</a> 173</li> <li><a href="https://btcmap.org/country/br">Brazil</a> 73</li> <li><a href="https://btcmap.org/country/cz">Czechia</a> 69</li> <li><a href="https://btcmap.org/country/de">Germany</a> 65</li> <li><a href="https://btcmap.org/country/at">Austria</a> 57</li> <li><a href="https://btcmap.org/country/gb">United Kingdom</a> 51</li> <li><a href="https://btcmap.org/country/jp">Japan</a> 46</li> <li><a href="https://btcmap.org/country/nl">Netherlands</a> 36</li> <li><a href="https://btcmap.org/country/ge">Georgia</a> 34</li> <li><a href="https://btcmap.org/country/us">United States of America</a> 34</li> </ol> <p>It&rsquo;s nice to see some good progress in Italy, and Brazil is still one of the most active countries on BTC Map. Most edits in Austria were authored by <a href="https://btcmap.org/tagger/17565129">descubrebitcoin</a>, and they seem to be ATMs. There is also a notable re-verification effort by <a href="https://btcmap.org/tagger/16990757">Bitcoinshire</a> in the United Kingdom.</p> <p>Most of the new places in Japan belong to <a href="https://cocorotus.com/map/">COCOROTUS FOODTRUCK</a> and I have no idea what it actually is, but they <a href="https://cocorotus.com/bitln/">mentioned us</a> on their website. Georgia is still exceptionally well-maintained, thanks to <a href="https://btcmap.org/tagger/13256444">empty_child</a>, and United States is slowly picking up steam, which is long overdue.</p> <p>Big thanks to <a href="https://btcmap.org/tagger/7522075">Rockedf</a> for contributing to almost every region!</p> <h2 id="trending-communities">Trending Communities</h2> <ol> <li>Satoshi Spritz 176</li> <li>Einundzwanzig Deutschland 64</li> <li>Bitcoin Austria 57</li> <li>Tokyo Citadel 45</li> <li>Bitcoin Association Switzerland 33</li> <li>Einundzwanzig Schweiz 33</li> <li>Dezentralschweiz 33</li> <li>Porto Alegre Bitcoin 31</li> <li>Einundzwanzig Portugal 30</li> <li>Bitcoin Bulgaria 23</li> </ol> <h2 id="global-metrics">Global Metrics</h2> <p>The number of verified merchants has dropped from 7,188 to 6,918 (-3.7%), which means that we need more contributors if we want all the regions to stay up-to-date. We&rsquo;re talking about ~300 additional verifications a month, which can be done even by a single dedicated contributor.</p> <p>The total number of merchants has increased from 11,215 to 11,521 (+2.8%), which is in line with our long-term trend. The stream of new merchants has no signs of tapering, we&rsquo;re just struggling to maintain the old ones in certain regions.</p> <p>The average number of days since the last verification has increased from 288 to 308 (+7%), which means that our data is now significantly less reliable than it was a month ago. This is yet another confirmation that our main bottleneck is the lack of local maintainers.</p> <h2 id="conclusion">Conclusion</h2> <p>We were able to improve our analytics, and we have a better understanding of the current trends on BTC Map. While the total number of Bitcoin-accepting merchants keeps growing at a consistent pace, we&rsquo;re struggling with re-verification of our existing merchants, which is supposed to be done at least once a year.</p> <p>The best way to help us is to re-verify some outdated locations in your area, if you have any:</p> <p><a href="https://btcmap.org/map?outdated">https://btcmap.org/map?outdated</a></p> Trip to Russia (After Five Years Away) https://bubelov.com/blog/2024/russia/ Mon, 12 Aug 2024 00:00:00 +0000 https://bubelov.com/blog/2024/russia/ <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#preface">Preface</a></li> <li><a href="#crossing-the-border">Crossing The Border</a></li> <li><a href="#moscow">Moscow</a></li> <li><a href="#samara">Samara</a></li> <li><a href="#kazan">Kazan</a></li> <li><a href="#sochi">Sochi</a></li> <li><a href="#opinions-on-events-in-ukraine">Opinions on Events in Ukraine</a></li> <li><a href="#sanctions">Sanctions</a></li> <li><a href="#trends-i-dont-like">Trends I Don&rsquo;t Like</a> <ul> <li><a href="#rampant-internet-censorship">Rampant Internet Censorship</a></li> <li><a href="#lower-quality-journalism">Lower Quality Journalism</a></li> <li><a href="#higher-tax-rates">Higher Tax Rates</a></li> <li><a href="#attitude-to-china">Attitude to China</a></li> </ul> </li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="preface">Preface</h2> <p>I hate cold weather, so I guess you could call me a climate refugee. About ten years ago, I moved to Thailand to escape the Russian winter, and I never looked back.</p> <p>That said, Russian summer is amazing, and I used to visit Moscow every year. Unfortunately, the COVID mess kept me away for a couple of years, and now the ongoing military operation in Ukraine has made things too uncertain to plan a trip.</p> <p>Now that things have settled a bit, I decided to go back to Russia for two months. Here are some thoughts from my trip.</p> <h2 id="crossing-the-border">Crossing The Border</h2> <p>Of course, the military operation is still ongoing, and figuring out the border rules was confusing. Both sides are pushing so much propaganda that it&rsquo;s hard to gauge your own personal risk.</p> <p>I&rsquo;d heard that people who&rsquo;ve been abroad too long can run into problems when they try to re‑enter, so I was ready for questioning and even a phone check. Those are still rare, but they happen in many countries, which justifies having a separate travel phone.</p> <p>Here&rsquo;s the reality: Russia&rsquo;s border is more open than ever. I didn&rsquo;t even have to talk to an immigration officer. Five years ago, both arrival and departure stamps were mandatory, but now it looks like they&rsquo;re not even required. I just scanned my passport at the machine, it gave me the go‑ahead, and that was it.</p> <p>Customs didn&rsquo;t check anyone. In fact, one guy outside even tried to sell me a SIM card under a fake identity, which feels pretty outrageous given the recent terrorist attacks in Moscow and across the country.</p> <h2 id="moscow">Moscow</h2> <p>Moscow was my first stop, and honestly, it hasn&rsquo;t changed much. It felt a little cleaner, but the biggest difference is how many Chinese cars there are now. They cover every price range, and they&rsquo;re actually really nice to drive. One guy I know sold his Lexus right after test driving a Zeekr 001, the difference is that obvious.</p> <p>I had an interesting talk with a guy from Tajikistan who moved to Russia a few months ago. He&rsquo;d also worked in the Czech Republic, Poland, and the UAE. We talked about jobs, pay, and racism, and he mentioned that xenophobia in Russia has gotten worse since the latest ISIS attack. He told me about a run-in with a cop during a random traffic stop. The officer was a jerk, and when this guy complained about the verbal abuse, he nearly got deported for talking back. Luckily, the judge sided with him and canceled the deportation order.</p> <p>He works in construction and drives a taxi, pulling in about $1,500–$2,000 a month after taxes. That doesn&rsquo;t sound like a lot, but he said it&rsquo;s the most he&rsquo;s ever made, more than in Europe or the Gulf. It makes sense when you think about it: living costs are still low in Russia, and taxes are pretty much non-existent for many workers. He&rsquo;s learned the language, plans to marry his girlfriend, and wants to start a family there.</p> <h2 id="samara">Samara</h2> <p>Samara was my next stop, and it&rsquo;s also my hometown. It&rsquo;s not exactly famous, but this is where most of Russia&rsquo;s domestic cars are built. They&rsquo;re struggling to compete with China, but thanks to protectionist policies, their cheapest models are more affordable. You can actually get a brand new car here for around $7,000.</p> <p>People in wealthier cities love to make fun of them, but these cars still dominate in less affluent areas. LADA has rolled out a few new models in the last couple of years, and they&rsquo;re starting to look usable:</p> <p><figure> <a href="https://bubelov.com/blog/2024/russia/vesta_hu_985376d439407dd0.webp"> <img src="https://bubelov.com/blog/2024/russia/vesta_hu_1bbd963fb467d243.webp" alt="" /> </a> </figure></p> <h2 id="kazan">Kazan</h2> <p>Russia is a federation, and the only way it holds together is by giving a lot of freedom to its ethnically and religiously diverse regions. Take Kazan, it&rsquo;s a beautiful city and the historic home of the Tatar people. While some Tatars are Orthodox, most are Muslim, and you can feel it in the city.</p> <p>Locals there take full advantage of that autonomy. Many of them only accept cash or direct transfers to their personal accounts. No receipts, no invoices. The federal government is clearly aware, but they don&rsquo;t crack down because pushing too hard could stir up separatism.</p> <p><figure> <a href="https://bubelov.com/blog/2024/russia/kazan_hu_d7c0ed4a8f388a35.webp"> <img src="https://bubelov.com/blog/2024/russia/kazan_hu_cbc872747bbde4d3.webp" alt="" /> </a> </figure></p> <h2 id="sochi">Sochi</h2> <p>Sochi&rsquo;s climate and architecture reminded me of Phuket, except Sochi is actually walkable. It&rsquo;s fairly well-developed, but you run into some annoying infrastructure problems, mostly from all the unregistered guesthouses running ACs full blast in the summer.</p> <p>The power went out on me a few times, so if you&rsquo;re staying in one of the cheap places on the outskirts like most tourists do, definitely bring a power bank.</p> <p>Sochi&rsquo;s popular with Russians looking for somewhere warm by the Black Sea. Unfortunately, the beaches don&rsquo;t have sand, just rocks. I’m not a big fan of rocky beaches, so if you actually want to swim in the sea, I&rsquo;d look at Turkey or Thailand instead. They&rsquo;re not much more expensive, and the experience is just better.</p> <p><figure> <a href="https://bubelov.com/blog/2024/russia/sochi_hu_16133450db8125e4.webp"> <img src="https://bubelov.com/blog/2024/russia/sochi_hu_7cce4b68bc4a744c.webp" alt="" /> </a> </figure></p> <p>That said, Sochi isn&rsquo;t just about the sun. It&rsquo;s also got a big theme park and a top-tier alpine ski resort. I visited <a href="https://en.wikipedia.org/wiki/Rosa_Khutor_Alpine_Resort">Rosa Khutor</a> while I was there, and I was really impressed, the views were stunning, and the whole place was surprisingly well built-out.</p> <p><figure> <a href="https://bubelov.com/blog/2024/russia/khutor_hu_a394ba888597bebb.webp"> <img src="https://bubelov.com/blog/2024/russia/khutor_hu_41fd320edee08542.webp" alt="" /> </a> </figure></p> <h2 id="opinions-on-events-in-ukraine">Opinions on Events in Ukraine</h2> <p>TLDR: People largely don&rsquo;t care.</p> <p>A lot of people imagine Russia as this hyper-centralized Orwellian hive, where everyone just nods along to whatever the state says. The reality is almost the exact opposite. Most people here, myself included, don&rsquo;t like Putin or the government in general, but that doesn&rsquo;t mean they&rsquo;d cheer for any random alternative, especially one tied to foreign interests.</p> <p>The current leadership is mostly seen as a compromise, and people are pretty clear-eyed about its pros and cons. The government knows this, too. It leans heavily on opinion polls and tries to address the biggest issues that cause public frustration, just to keep things stable.</p> <p>It was surprisingly hard to find Russians who even wanted to talk about the conflict in Ukraine. And it had nothing to do with fear or oppression, not a single person I met felt that way. To get it, you have to understand the distance between people and the state here. If you&rsquo;re detached from your own government in your everyday life, why would you care about events abroad?</p> <p>Sure, there are a few NAFO-style freaks who are totally consumed by state propaganda, but I never heard aggressive talk from actual war vets or from anyone who actually thinks for themselves.</p> <p>In Sochi, I met a guy who fought in Lugansk and got badly wounded in the legs. He can walk, but it&rsquo;s not easy anymore. You never hear any bravado from guys like him. They actually respect their opponents and don&rsquo;t waste time talking trash about western weapons or Ukrainian soldiers, unlike the armchair warriors who are all in on the propaganda.</p> <p>He told me he regrets volunteering. Money was his main reason for signing up.</p> <p>Personally, I felt the same way for a long time. I used to sympathize with Ukraine, my family&rsquo;s from there, and I had a mostly positive view of the west. But if you&rsquo;re trying to stay rational and follow the truth, you have to change your mind when the facts change.</p> <p>I&rsquo;ve always known the last Maidan was a coup, and honestly, that never bothered me. I hoped Ukraine would use it as a chance to liberalize and build its economy, something that could&rsquo;ve even been good for Russia by setting a positive example.</p> <p>But the only things the post-coup government actually grew were poverty, corruption, and Nazi ideology. Watching that happen shifted my view from cautiously hopeful to completely indifferent.</p> <p>The west showed its true colors, too, feeding Ukraine empty promises while hitting ordinary Russians that have nothing to do with the war, with various sanctions. It didn&rsquo;t really affect me, but I know good families who got hurt.</p> <p>My thinking is pretty simple: I try to be nice to people by default. But if someone tries to harm me, or backs those who do, I&rsquo;ll support anyone who makes them pay.</p> <p>My biggest personal takeaway is this: you may hate your government, and you might have good reasons, but you&rsquo;ll always be treated worse anywhere else. No other country will give you more rights or freedoms than your own, so you should never turn against it, and you should think long-term about your national interests.</p> <p>Politicians come and go. Putin isn&rsquo;t immortal, and he wasn&rsquo;t wrong when he said the west is full of shit. Hopefully, he&rsquo;ll be replaced by someone more capable. But I doubt the west would be happy with a stronger Russian leader since power is often a zero-sum game, and today&rsquo;s western elites don&rsquo;t want a strong, sovereign Russia.</p> <h2 id="sanctions">Sanctions</h2> <p>Anyone who says Russia is thriving is either lying or just clueless. Same goes for anyone who claims it&rsquo;s in ruins. Honestly, not much has really changed. All the talk about western brands pulling out is a joke, I had no trouble finding trendy western stuff in Russia. In fact, there’s even more choice than here in Thailand.</p> <p>The truth is, you can&rsquo;t really sanction an advanced economy with large, educated population. Low taxes and open markets fix problems fast. When Visa and Mastercard left, it looked like a disaster, until you remember why Russia built its own payment system, MIR Pay. It works perfectly, and payments go through instantly, probably because the servers are just down the road now.</p> <p><figure> <a href="https://bubelov.com/blog/2024/russia/fastfood_hu_ab6e0d02cfd28e9c.webp"> <img src="https://bubelov.com/blog/2024/russia/fastfood_hu_61318e73fd10e849.webp" alt="" /> </a> <figcaption>Starbucks, McDonalds and KFC in a rural food court</figcaption> </figure></p> <h2 id="trends-i-dont-like">Trends I Don&rsquo;t Like</h2> <p>I care most about individual rights and freedoms, which means that I don&rsquo;t like any government since they all are parasitic and predatory.</p> <p>Here are some of the darker trends I&rsquo;ve seen in Russia. Most of them aren&rsquo;t unique to here, though:</p> <h3 id="rampant-internet-censorship">Rampant Internet Censorship</h3> <p>Both Proton Mail and Twitter are blocked in Russia, so you&rsquo;ll need a reliable VPN to get around it. That was probably the most annoying part of my trip. Thankfully, there are plenty of VPN services that work well, and you can even pay with bitcoin over the Lightning Network. Necessity really is the mother of invention.</p> <p>This is part of a global trend, and it&rsquo;s pretty alarming, especially if using a VPN becomes a crime down the line. The worst-case scenario is when countries lock their own people out of information. We need better ways to bypass internet censorship.</p> <h3 id="lower-quality-journalism">Lower Quality Journalism</h3> <p>All the independent media in Russia are gone now, so the only reliable source of news is a handful of trusted Telegram channels. If Telegram gets blocked, people will be left in the dark. These days, doing political journalism inside Russia is almost suicidal, your choices are to leave the country or stick to less risky topics like local news.</p> <p>It&rsquo;s not like journalism is thriving globally, but self-censorship in Russia is definitely more advanced. From the state&rsquo;s point of view, it makes some sense: a lot of political journalists were funded by foreign interests, and they spread harmful lies in sensitive time. More censorship was the result. Sadly, a few honest journalists got caught in the crossfire.</p> <h3 id="higher-tax-rates">Higher Tax Rates</h3> <p>Right now, individuals and small businesses pay between 6% and 13% in income tax, depending on how you&rsquo;re registered. The standard rate is a flat 13%, unless you apply for the lower 6% rate as self-employed or a small business. A flat tax is pretty unusual for a country, honestly.</p> <p>Starting in 2025, Russia is switching to a progressive tax, with a top rate of 22%. It&rsquo;s the worst thing to happen in years, the last thing Russia needs is to adopt the EU-style tax model. But the direction is clear.</p> <p>Some people naively think taxes will drop after the war, but that’s not how governments work. Once they get used to taking more money, they never give it back. A government is like a cancer, it only spreads.</p> <h3 id="attitude-to-china">Attitude to China</h3> <p>Many Russians do love China nowadays, and I have mixed feelings about that. It&rsquo;s not that I have anything against China, but I understand that each country is obliged to prioritize the interests of their people, and many things are in fact zero-sum games. In other words, it&rsquo;s dangerous to depend too much on any single country, China is definitely our situational ally, but we need to diversify.</p> <h2 id="conclusion">Conclusion</h2> <p>I really enjoyed my trip and felt as safe as ever, but having to use a VPN to get around pointless internet censorship was annoying. Moscow is still the only Russian city where I&rsquo;d want to live long-term. In fact, during the summer, it’s the most comfortable place I know, anywhere in the world.</p> <p>Still, a lot of things aren&rsquo;t getting better, and some trends are genuinely concerning. It&rsquo;s hard to predict where things go from here, but I&rsquo;m leaning pessimistic. I don&rsquo;t believe you can change a system from the inside, any group in power will always want more control and end up taking more from everyone else.</p> <p>That&rsquo;s why I think political change is a dead end. Our energy is better spent building tools that give power back to people. Things like Bitcoin. It&rsquo;s our best shot at limiting governments and holding onto our freedoms.</p> BTC Map Weekly Recap https://bubelov.com/blog/2024/btcmap-weekly-05-19/ Sun, 19 May 2024 00:00:00 +0000 https://bubelov.com/blog/2024/btcmap-weekly-05-19/ <p><a href="https://bubelov.com/blog/2024/btcmap-weekly-05-12/">Previous post</a></p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#help-needed-delivery">Help Needed: Delivery</a></li> <li><a href="#key-metrics">Key Metrics</a></li> <li><a href="#whats-next-kotlin-multiplatform">What&rsquo;s Next: Kotlin Multiplatform</a></li> </ul> </nav> </div> <h2 id="help-needed-delivery">Help Needed: Delivery</h2> <p>Enabling you to spend sats in your local shops is our core mission, but it would be wrong to limit your shopping options while many remote shops offer nationwide and global delivery. The most important thing to understand about BTC Map, is that it&rsquo;s only as good as its data and its community. So, let&rsquo;s take a look at our delivery data:</p> <p><a href="https://overpass-turbo.eu/s/1LH0">https://overpass-turbo.eu/s/1LH0</a></p> <p>There are less than 100 places with a <a href="https://wiki.openstreetmap.org/wiki/Key:delivery">delivery</a> tag, most of them being cafes and restaurants, so there is noting to show to BTC Map users yet. I&rsquo;m pretty sure there are thousands of places which can send you all kinds of stuff for sats, we just need to find them and tag them, and we need your help. All you need to do is to add this tag to the places you know, and we&rsquo;ll take care of showing those places in our web and mobile apps.</p> <h2 id="key-metrics">Key Metrics</h2> <p><figure> <a href="https://bubelov.com/blog/2024/btcmap-weekly-05-19/verified_hu_75d0ae4ee96ab289.webp"> <img src="https://bubelov.com/blog/2024/btcmap-weekly-05-19/verified_hu_ed0e8df2a6989e45.webp" alt="" /> </a> </figure></p> <p>The number of verified merchants has dropped by 54, which means we&rsquo;re struggling to support more than ~7,000 up-to-date merchants globally. In order to stay reasonably up-to-date and reliable, a place needs to be re-verified at least once a year, so let&rsquo;s do some simple math.</p> <p>How many places do we need to verify daily in order to have 7,000 verified places every year? 7,000 / 365 ~= 20. If the number of verified places is dropping, it means we don&rsquo;t have enough contributors to verify at least 20 places daily, on average. Let&rsquo;s hope that this situation will improve in the near future.</p> <p><figure> <a href="https://bubelov.com/blog/2024/btcmap-weekly-05-19/total_hu_6e3a2071d0449e3d.webp"> <img src="https://bubelov.com/blog/2024/btcmap-weekly-05-19/total_hu_d1b0af619f070a67.webp" alt="" /> </a> </figure></p> <p>The total number of merchants has increased by 73, which is in line with our long-term trend. The stream of new merchants has no signs of tapering, we&rsquo;re just struggling to maintain the old ones.</p> <p><figure> <a href="https://bubelov.com/blog/2024/btcmap-weekly-05-19/days-since-verified_hu_160fa74181a3df0d.webp"> <img src="https://bubelov.com/blog/2024/btcmap-weekly-05-19/days-since-verified_hu_cd445e4dd835a6b1.webp" alt="" /> </a> </figure></p> <p>The average number of days since the last verification increased from 268 to 270, which means that our data is now slightly less reliable than it was a week ago. This is yet another confirmation that our main bottleneck is the lack of local maintainers.</p> <h2 id="whats-next-kotlin-multiplatform">What&rsquo;s Next: Kotlin Multiplatform</h2> <p>While BTC Map is available on iOS, our iPhone app lacks many important features and performance updates, and this feature gap will only widen with time. The reason is simple: we don&rsquo;t have any permanent iOS devs, and we can&rsquo;t be certain that this issue will be resolved any time soon.</p> <p>Luckily, we have a reasonably up-to-date web app, which works on every platform, but web apps are lacking certain features which are eiter very valuable or absolutely critical to us, namely:</p> <ul> <li>BTC Map should not require a server, centralization is both risky and expensive</li> <li>BTC Map should not require you to trust us, but you can&rsquo;t avoid trusting us if you blindly fetch any HTML and JS which our server sends to your browser tab</li> <li>It&rsquo;s not even our server, the web app is hosted on Netlify</li> <li>We need to minimize and tightly control any network transfers</li> <li>We want our code to be easily inspectable and digitally signed by the devs or third-party distributors such as F-Droid</li> <li>Native apps are closer to hardware, which leads to better performance, all else being equal, which is especially important on low-end hardware</li> </ul> <p>There are many other reasons to have native apps, but there are also reasons to have a web app:</p> <ul> <li>Not having enough contributors on certain platforms, our iOS situation is the latest example</li> <li>Our native apps are aimed at regular users, and they&rsquo;re optimized for small screens. The web app can do that too, but power user / community admin features is where it stands out</li> <li>Redundancy is extremely important. It&rsquo;s not too far-fetched to assume that our native apps can be banned by App Store and Google Play, so it&rsquo;s important to have additional options</li> </ul> <p>We believe that the best way forward is to try merging our Android and iOS code bases as much as possible, which should make it easier to support them and even introduce new platforms such as desktop or PinePhone. <a href="https://kotlinlang.org/docs/multiplatform.html">Kotlin Multiplatform</a> seems like our best bet since our Android code base is in a good shape, and it can be gradually refactored in a platform-agnostic way.</p> <p>Unlike many other cross-platform options, Kotlin Multiplatform doesn&rsquo;t force us to sacrifice app performance, and we can keep using native UI components of every target platform in order to have the best look and feel and a responsive UI.</p> <p>I&rsquo;ve been busy building a multiplatform database layer recently, and I stumbled upon a few issues, but they aren&rsquo;t critical, so we&rsquo;ll continue with this effort, and hopefully we&rsquo;ll be able to have feature parity between all the native platforms in the long run.</p> BTC Map Weekly Recap https://bubelov.com/blog/2024/btcmap-weekly-05-12/ Sun, 12 May 2024 00:00:00 +0000 https://bubelov.com/blog/2024/btcmap-weekly-05-12/ <p><a href="https://bubelov.com/blog/2024/btcmap-weekly-05-05/">Previous post</a></p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#api-v3">API V3</a></li> <li><a href="#key-metrics">Key Metrics</a></li> <li><a href="#whats-next-delivery">What&rsquo;s Next: Delivery</a></li> <li><a href="#whats-next-improving-resilience">What&rsquo;s Next: Improving Resilience</a></li> </ul> </nav> </div> <h2 id="api-v3">API V3</h2> <p>BTC Map always had an offline-first architecture, meaning that you don&rsquo;t need an Internet connection in order to see all the bitcoin-accepting merchants in your area or even globally. Our users are the only real owners of their apps and their copies of BTC Map data, they can keep it forever and there is nothing we can do to change that.</p> <p>That said, fetching the recent changes and updates is often desirable, as well as downloading new map tiles to cover more regions. BTC Map API is strictly read only, and it&rsquo;s sole purpose is to provide the most recent changes to all the interested users. Keeping all the data locally has its price, and the storage and transfer requirements are growing together with global Bitcoin adoption.</p> <p>API V3 is our latest attempt to optimize the size of the data which needs to be transferred in order to keep your data snapshot up to date. My tests are showing 30% reduction in data transfers, so you only need to fetch ~3 KB a day in order to stay up to date. It shouldn&rsquo;t be a big deal even if you&rsquo;re using Tor or a chain of VPN servers.</p> <h2 id="key-metrics">Key Metrics</h2> <p><figure> <a href="https://bubelov.com/blog/2024/btcmap-weekly-05-12/verified_hu_f6c020e6a05f11e5.webp"> <img src="https://bubelov.com/blog/2024/btcmap-weekly-05-12/verified_hu_496a65be0a68b99a.webp" alt="" /> </a> </figure></p> <p>The number of verified merchants has increased by 60, which is a significant improvement both in terms of quality and quantity. Most locations were verified by <a href="https://twitter.com/RockeBTC">Rockedf</a> and the local mappers.</p> <p>Here are the top 5 countries by the number of new merchants:</p> <ul> <li>Brazil</li> <li>Australia</li> <li>South Africa</li> <li>Switzerland</li> <li>El Salvador</li> </ul> <p><figure> <a href="https://bubelov.com/blog/2024/btcmap-weekly-05-12/total_hu_90c66f59eb3d3516.webp"> <img src="https://bubelov.com/blog/2024/btcmap-weekly-05-12/total_hu_555a4a98a8b7de68.webp" alt="" /> </a> </figure></p> <p>The total number of merchants has increased by 39, which is slightly below average. The main reason is Argentina data cleanup, it had many outdated merchants so their removal boosted our signal-to-noise ratio in that region.</p> <p><figure> <a href="https://bubelov.com/blog/2024/btcmap-weekly-05-12/days-since-verified_hu_4948084d4837dbc0.webp"> <img src="https://bubelov.com/blog/2024/btcmap-weekly-05-12/days-since-verified_hu_c2d8fb3148c38616.webp" alt="" /> </a> </figure></p> <p>The average number of days since the last verification decreased from 269 to 267, which serves as another confirmation of our improving data reliability. I expected no change, so I once again underestimated the local mappers.</p> <h2 id="whats-next-delivery">What&rsquo;s Next: Delivery</h2> <p>The problem: many users are living in areas with a low number of local bitcoin-accepting merchants. While this number is growing steadily, life is short, and people want to build circular economies and escape the fiat system ASAP.</p> <p>The solution: we need to identify all the merchants who offer either nationwide or global delivery, which would significantly expand the choice of goods and services available to an average BTC Map user. Of course, distance requires trust, and that&rsquo;s one of the reasons we don&rsquo;t support pure services with no physical location. If you made an order, and you aren&rsquo;t happy with the result, it shouldn&rsquo;t be possible for a merchant to avoid any responsibility. Please note that we can&rsquo;t really guarantee that all locations are real, the final judgement call is on you, you should carefully investigate merchants before making remote orders and sending your sats God knows where.</p> <h2 id="whats-next-improving-resilience">What&rsquo;s Next: Improving Resilience</h2> <p>BTC Map client apps are pretty resilient already, but they tend to depend heavily on the following components:</p> <ul> <li>Tile servers (for rendering maps)</li> <li>BTC Map API (for merchant data updates)</li> </ul> <p>In light mode, the app uses OSM tile servers, and we&rsquo;re also hosting Stadia Maps tile server in case you&rsquo;re joined the dark side. It would be cool to give the users more choices, as well as to allow setting their own tile server URLs. We can&rsquo;t embed the tiles into the app, because a full tile set would require terabytes of storage, so the second-best thing is to let the users choose their own tile servers, in case the default options aren&rsquo;t available or aren&rsquo;t desirable for some reason. Some people may even prefer to self-host their own map tiles, self-hosting is not uncommon among hardcore bitcoiners who value their privacy and don&rsquo;t want anyone to know which map regions they viewed.</p> <p>Our API is fully open source, so you can also self-host your own instance, if you feel like it. While you can do it by recompiling the app, it&rsquo;s better to let users change their API URL in settings.</p> BTC Map Weekly Recap https://bubelov.com/blog/2024/btcmap-weekly-05-05/ Sun, 05 May 2024 00:00:00 +0000 https://bubelov.com/blog/2024/btcmap-weekly-05-05/ <p><a href="https://bubelov.com/blog/2024/btcmap-weekly-04-28/">Previous post</a></p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#new-feature-data-bundling">New Feature: Data Bundling</a></li> <li><a href="#new-feature-companion-app-warnings">New Feature: Companion App Warnings</a></li> <li><a href="#new-feature-navigate-to-merchant">New Feature: Navigate to Merchant</a></li> <li><a href="#new-feature-sharing-merchants">New Feature: Sharing Merchants</a></li> <li><a href="#key-metrics">Key Metrics</a></li> <li><a href="#whats-next-syncing-in-adverse-conditions">What&rsquo;s Next: Syncing in Adverse Conditions</a></li> </ul> </nav> </div> <h2 id="new-feature-data-bundling">New Feature: Data Bundling</h2> <p>It&rsquo;s hard not think about building resilient software in the light of the recent events, that&rsquo;s why we decided to include the most recent data snapshots with every new BTC Map release. In short, if our servers go down, for whatever reason, you won&rsquo;t be affected. It&rsquo;s also impossible to censor us via Google Play, since the app is available on F-Droid, GitHub, and it can also be built from the source.</p> <p>Data bundling also speeds up the initial sync by an order of magnitude!</p> <h2 id="new-feature-companion-app-warnings">New Feature: Companion App Warnings</h2> <p>You only need a Lightning wallet in order to pay to the vast majority of our merchants, but there are two exceptions:</p> <ul> <li>Czech Republic (Querko restaurants)</li> <li>South Africa (Pick n Pay supermarkets)</li> </ul> <p>Some of the places do require a third party companion app in order to generate BOLT 11 invoices, so you need to install those apps if you want to pay with bitcoins. Usually, we don&rsquo;t tolerate that, but perfect is the enemy of good, and we decided to accept them on the following conditions:</p> <ul> <li>The app should generate a real Lightning invoice, payable with any LN wallet of user&rsquo;s choice</li> <li>KYC is not acceptable under any circumstances</li> </ul> <p>Those places didn&rsquo;t have proper warnings, so I fixed it in the latest release:</p> <p><figure> <a href="https://bubelov.com/blog/2024/btcmap-weekly-05-05/warning_hu_bbb5219f751a743b.webp"> <img src="https://bubelov.com/blog/2024/btcmap-weekly-05-05/warning_hu_5c9c7325a6dc3431.webp" alt="" /> </a> </figure></p> <h2 id="new-feature-navigate-to-merchant">New Feature: Navigate to Merchant</h2> <p>Building routes is hard, that&rsquo;s why we aren&rsquo;t doing it. You need to know a lot of things, including traffic jams, accidents, planned roadworks and so on. I know it well because I used to work for a taxi company which operated in London. Still, many users want to be able to quickly navigate to a selected merchant, so I decided to delegate this task to an external app. Most of the solutions on StackOverflow were dependent on Google Maps, but there was an interesting solution which seems universal. It appears to be working, but we&rsquo;ll need more testing and user feedback to confirm that.</p> <h2 id="new-feature-sharing-merchants">New Feature: Sharing Merchants</h2> <p>I don&rsquo;t know why I waited so long, but it seems to be a must-have feature. As a power user, you don&rsquo;t really miss it, since you can always open a place on OSM and see the nifty-gritty details, but it&rsquo;s not a good way of sharing a bitcoin-accepting place with your friends.</p> <h2 id="key-metrics">Key Metrics</h2> <p><figure> <a href="https://bubelov.com/blog/2024/btcmap-weekly-05-05/verified_hu_aaf42c61dfc2ea11.webp"> <img src="https://bubelov.com/blog/2024/btcmap-weekly-05-05/verified_hu_55561e8f7e7abf32.webp" alt="" /> </a> </figure></p> <p>The number of verified merchants has increased by 41, half of the last week&rsquo;s number. Most locations were verified by <a href="https://twitter.com/RockeBTC">Rockedf</a> and the local mappers.</p> <p>Here are the top 5 countries by the number of new merchants:</p> <ul> <li>Brazil</li> <li>Spain</li> <li>Germany</li> <li>Czech Republic</li> <li>The Netherlands</li> </ul> <p><figure> <a href="https://bubelov.com/blog/2024/btcmap-weekly-05-05/total_hu_75a721a546a45e1d.webp"> <img src="https://bubelov.com/blog/2024/btcmap-weekly-05-05/total_hu_5d422d52f8dacef9.webp" alt="" /> </a> </figure></p> <p>The total number of merchants has increased by 8, which is unusually small. The main reason is Boracay data cleanup which was done by a <a href="https://pouch.ph/">Pouch</a> team. They had many outdated merchants so their removal boosted our signal-to-noise ratio in that region.</p> <p><figure> <a href="https://bubelov.com/blog/2024/btcmap-weekly-05-05/days-since-verified_hu_8b3ccb6a113d13ba.webp"> <img src="https://bubelov.com/blog/2024/btcmap-weekly-05-05/days-since-verified_hu_cfc80fa90b9e97cc.webp" alt="" /> </a> </figure></p> <p>The average number of days since the last verification didn&rsquo;t change, which means that our data is as reliable as it was a week ago. I expected a decline since I didn&rsquo;t do any mapping, but it looks like the locals are finally starting to maintain their own data, which reduces our dependency on remote edits done by a few of our team members.</p> <h2 id="whats-next-syncing-in-adverse-conditions">What&rsquo;s Next: Syncing in Adverse Conditions</h2> <p>Next week, I want to focus on our server software and infrastructure. The main goal is to release a v3 API, which is supposed to improve sync speed and lower the bandwidth requirements. Users like snappy software, but the real driving factor is the ability to make our API usable via TOR and other privacy preserving overlay networks. Even a normal VPN can slow down your network connection to the point where every byte counts, so it makes sense to make our API as lightweight as possible.</p> BTC Map Weekly Recap https://bubelov.com/blog/2024/btcmap-weekly-04-28/ Sun, 28 Apr 2024 00:00:00 +0000 https://bubelov.com/blog/2024/btcmap-weekly-04-28/ <p><a href="https://bubelov.com/blog/2024/btcmap-weekly-04-21/">Previous post</a></p> <h2 id="new-merchant-notifications">New Merchant Notifications</h2> <p>Adding your shop to BTC Map will now notify all the users within a 100 km radius, so they&rsquo;ll be able to discover your shop in no time. As a user, don&rsquo;t forget to send us your feedback when you visit new places, it&rsquo;s very important to us, and we often boost the most trusted and popular places for free, so your feedback is a good way to support BTC Map merchants. We also have a few shady and unreliable places, but we can&rsquo;t remove them unless we have a report from a local user, so both positive and negative user feedback regarding our locations is the best way to support BTC Map and help your fellow bitcoiners.</p> <p><figure> <a href="https://bubelov.com/blog/2024/btcmap-weekly-04-28/new-merchants_hu_9a6a9316892d7e6c.webp"> <img src="https://bubelov.com/blog/2024/btcmap-weekly-04-28/new-merchants_hu_b89e57bb3fc8d9be.webp" alt="" /> </a> </figure></p> <h2 id="legacy-data-is-no-more">Legacy Data is No More</h2> <p>This week concludes the end of my ancient data verification push. My goal was to re-verify all the places which were added before the latest BTC Map restart (September 2022).</p> <p>Some users might not know that, but BTC Map existed since 2015, and it even featured a bunch of shitcoins. I stumbled upon coinmap.org back in 2015, and I really liked the idea. I think coinmap was made during some hackathon in Eastern Europe, so it was probably done in a rush, and then it was quickly abandoned. I did some basic reverse engineering and I figured out that coinmap just fetched the data from OSM, and then it got out of sync and started to rot. Lesson learned, it&rsquo;s better not to fork OSM datasets, unless you want to maintain the whole world by yourself.</p> <p>Taking an &ldquo;add whatever you want, I don&rsquo;t care&rdquo; stance on shitcoins was a mistake, and onchain Bitcoin transactions didn&rsquo;t really fit the retail use cases, so the project was quickly abandoned, and it was also too boring to do that alone. I regained my interest in promoting Bitcoin as a medium of exchange in spring 2022, after some experimentation with the Lightning Network. This time it wasn&rsquo;t just an Android app, there are many other components which are created and maintained by other bitcoiners from different parts of the world. Hopefully, I&rsquo;ll be able to gradually disappear and focus on the other, more technical bottlenecks in Bitcoin infra, but I feel like our core team is still too small and fragile to do that safely, so I&rsquo;m not going anywhere unless I&rsquo;m confident that the project won&rsquo;t fade away after losing a couple of active contributors.</p> <h2 id="key-metrics">Key Metrics</h2> <p><figure> <a href="https://bubelov.com/blog/2024/btcmap-weekly-04-28/verified_hu_76318108452ad0d9.webp"> <img src="https://bubelov.com/blog/2024/btcmap-weekly-04-28/verified_hu_7d3b2e62f31b0233.webp" alt="" /> </a> </figure></p> <p>The number of verified merchants has increased by 81, which is about the same as last week. Most locations were verified by <a href="https://twitter.com/RockeBTC">Rockedf</a> and me, but we also had a significant number of verifications made by the locals.</p> <p>Here are the top 5 countries by the number of new merchants:</p> <ul> <li>Italy</li> <li>Brazil</li> <li>United Kingdom</li> <li>Germany</li> <li>France</li> </ul> <p><figure> <a href="https://bubelov.com/blog/2024/btcmap-weekly-04-28/total_hu_edd81e52e1533a37.webp"> <img src="https://bubelov.com/blog/2024/btcmap-weekly-04-28/total_hu_13e63b8eeb277e64.webp" alt="" /> </a> </figure></p> <p>The total number of merchants has increased by 66, which is a healthy trend.</p> <p><figure> <a href="https://bubelov.com/blog/2024/btcmap-weekly-04-28/days-since-verified_hu_ee6c53902dda02c3.webp"> <img src="https://bubelov.com/blog/2024/btcmap-weekly-04-28/days-since-verified_hu_6f9daa41646df790.webp" alt="" /> </a> </figure></p> <p>The average number of days since the last verification dropped from 271 to 268, which means that our data is slightly more reliable now. This is a global average, and regional data reliability can vary. You can always find your community or a country in order to check the local trends.</p> <h2 id="whats-next-decentralized-data-ownership">What&rsquo;s Next: Decentralized Data Ownership</h2> <p>I believe we built enough tools to enable locals to take control of their regions. From now on, I will limit my global edits and focus on maintaining Thai merchants since I&rsquo;ve been living here for 10 years, and it&rsquo;s my current country of residence. Thai data is in a good shape already, but I want to learn more about all the merchants and there are many other useful tags except <code>currency:XBT</code> which need to be surveyed.</p> <p>Since we still don&rsquo;t have enough editors, I expect many regions to disappear from the map in the long run, and I don&rsquo;t really see it as a problem. We gave them everything to kickstart their regions and get a feel of the future, now it&rsquo;s up to them.</p> <blockquote> <p>The future is already here – it&rsquo;s just not evenly distributed. ― William Gibson</p> </blockquote> BTC Map Weekly Recap https://bubelov.com/blog/2024/btcmap-weekly-04-21/ Sun, 21 Apr 2024 00:00:00 +0000 https://bubelov.com/blog/2024/btcmap-weekly-04-21/ <p><a href="https://bubelov.com/blog/2024/btcmap-weekly-04-14/">Previous post</a></p> <p>Data quality remains the main bottleneck, followed by the discoverability of the new merchants.</p> <h2 id="key-metrics">Key Metrics</h2> <p><figure> <a href="https://bubelov.com/blog/2024/btcmap-weekly-04-21/verified_hu_e895ec2e0ecbc94a.webp"> <img src="https://bubelov.com/blog/2024/btcmap-weekly-04-21/verified_hu_fd97ef98b89ada1d.webp" alt="" /> </a> </figure></p> <p>We added 93 verified merchants, which is about the same as last week. Most locations were verified by <a href="https://twitter.com/RockeBTC">Rockedf</a> and me, but we also had a significant number of verifications made by the locals.</p> <p>Most of the new places are located in Brazil, Italy and Germany.</p> <p><figure> <a href="https://bubelov.com/blog/2024/btcmap-weekly-04-21/total_hu_a1f0f668070a5aa0.webp"> <img src="https://bubelov.com/blog/2024/btcmap-weekly-04-21/total_hu_f0767dae7804c11c.webp" alt="" /> </a> </figure></p> <p>The total number of merchants dropped by 112, mostly due to my re-verification of extremely outdated merchants. It certainly boosted our signal-to-noise ratio.</p> <p><figure> <a href="https://bubelov.com/blog/2024/btcmap-weekly-04-21/days-since-verified_hu_f782e5b1cc9e0dc5.webp"> <img src="https://bubelov.com/blog/2024/btcmap-weekly-04-21/days-since-verified_hu_cde3ef60875e13ed.webp" alt="" /> </a> </figure></p> <p>The average number of days since the last verification dropped from 307 to 272, which is a very significant improvement. In the long run, this metric will become our key data health indicator, but we need to deal with the remaining outliers first.</p> <h2 id="extreme-outliers">Extreme Outliers</h2> <p>We still have some ancient places, and it&rsquo;s our biggest problem. The date of the oldest verification was December 2017 in the end of last week, and now it&rsquo;s April 2021.</p> <h2 id="android-app-background-sync">Android App: Background Sync</h2> <p>Background sync is working reliably, and it will be included in the next Android app release. I hope the Web and iOS clients will follow. The app should always show you the latest data as fast as possible, no matter how long you were absent.</p> <h2 id="whats-next-local-activity-notifications">What&rsquo;s Next: Local Activity Notifications</h2> <p>BTC Map doesn&rsquo;t have any tracking capabilities, which is extremely rare for a modern app. We don&rsquo;t know how many users we have, and we know nothing about our users. Our server is read-only, which means that we can send you new data if you ask us for it, but we never send any data back to the server.</p> <p>Most notifications are initiated by the server, but we can&rsquo;t do it, since we aren&rsquo;t using Google or Apple notification infrastructure, and we don&rsquo;t even know where to send those notifications, because the recipient address is user data, and we don&rsquo;t store user data.</p> <p>It is also possible to send a notification from the local app itself, so, while we don&rsquo;t have your private data, the app on your phone can have it and can use it to notify you. If you granted BTC Map your GPS location access, it doesn&rsquo;t mean our servers have it. In fact, your location will never leave your device, and you can verify it yourself by examining the source code. Since the app can know your location, it can notify you of new merchants nearby, if it finds them during the background sync. That&rsquo;s what I&rsquo;ll focus on next week.</p> BTC Map Weekly Recap https://bubelov.com/blog/2024/btcmap-weekly-04-14/ Sun, 14 Apr 2024 00:00:00 +0000 https://bubelov.com/blog/2024/btcmap-weekly-04-14/ <p><a href="https://bubelov.com/blog/2024/outliers/">Previous post</a></p> <p>I&rsquo;m not a big fan of mapping, but some things just need to be done. Once we attract more local editors, I&rsquo;ll be able to focus on a more technical stuff, but data quality is the main bottleneck at the moment.</p> <h2 id="key-metrics">Key Metrics</h2> <p><figure> <a href="https://bubelov.com/blog/2024/btcmap-weekly-04-14/verified_hu_ff9e8048ee6f5cda.webp"> <img src="https://bubelov.com/blog/2024/btcmap-weekly-04-14/verified_hu_a9dfc45dc0fb9473.webp" alt="" /> </a> </figure></p> <p>We added 102 verified merchants, which is two times more than we added during the last week.</p> <p><figure> <a href="https://bubelov.com/blog/2024/btcmap-weekly-04-14/total_hu_72782f7ed9eccd8.webp"> <img src="https://bubelov.com/blog/2024/btcmap-weekly-04-14/total_hu_b182ad2c5ed75205.webp" alt="" /> </a> </figure></p> <p>The total number of merchants dropped by 64, mostly due to my re-verification of extremely outdated merchants. It certainly boosted our signal-to-noise ratio.</p> <p><figure> <a href="https://bubelov.com/blog/2024/btcmap-weekly-04-14/days-since-verified_hu_519988885038a4b8.webp"> <img src="https://bubelov.com/blog/2024/btcmap-weekly-04-14/days-since-verified_hu_29a861db78a3cceb.webp" alt="" /> </a> </figure></p> <p>The average number of days since the last verification dropped from 354 to 315, which is a very significant improvement. In the long run, this metric will become our key data health indicator, but we need to deal with the remaining outliers first.</p> <h2 id="extreme-outliers">Extreme Outliers</h2> <p>We still have some ancient places, and it&rsquo;s our biggest problem. The date of the oldest verification was April 2015 in the end of last week, and now it&rsquo;s December 2017.</p> <h2 id="outdated-merchants">Outdated Merchants</h2> <p>It&rsquo;s a really nice feature introduced by secondl1ght:</p> <p><a href="https://btcmap.org/map?outdated">https://btcmap.org/map?outdated</a></p> <p>Opening this link allows you to see all the outdated merchants around you. It&rsquo;s very handy for anyone who wants to verify their local merchants. We are trying to make it as easy as possible for people to take ownership of their local data.</p> <h2 id="in-progress-background-sync">In Progress: Background Sync</h2> <p>Every time you open BTC Map, it needs to download all the changes since your last visit. The overall sync performance depends on how long you were absent, and the quality of your Internet connection. That&rsquo;s not really a big deal, but it doesn&rsquo;t allow us to interact with the users asynchronously. Let&rsquo;s say someone added a new bar in my area, but I won&rsquo;t know about that until I open BTC Map again. Having a background sync would make sure that I will be able to see all the new places immediately after opening the app, but it also clears the patch to asynchronous notifications.</p> <h2 id="in-progress-local-activity-notifications">In Progress: Local Activity Notifications</h2> <p>We&rsquo;ll start with notifying the users of any new bitcoin-accepting merchants nearby, but it&rsquo;s just the beginning. We want to decentralize map editing, which means that the locals should be in charge of their data. For instance, we can create an opt-in notification channel which will notify local maintainers of any new issues with their data. It would also allow them to remove all the spam and scam promptly.</p> <p>We can also make boosts more appealing by notifying the users of newly boosted places. Boosts aren&rsquo;t really a significant source of money for BTC Map, but they also act as a proof that a merchant is serious about accepting bitcoins and that it has a functional Lightning wallet.</p> BTC Map Timestamps & Outliers https://bubelov.com/blog/2024/outliers/ Sun, 07 Apr 2024 00:00:00 +0000 https://bubelov.com/blog/2024/outliers/ <p>Every BTC Map merchant has a last check date, which is used to figure out if the data is still reliable. Most merchants were verified less than a year ago, but we also have a small number of extreme outliers. This week, I was busy verifying the oldest places in our dataset.</p> <p>The oldest verification date was April 2014, and now it&rsquo;s April 2015, which is a noticeable improvement. Unfortunately, about 90% of those places were either out of business or no longer accepting bitcoins.</p> <p>Here are a few interesting weekly metrics:</p> <p><figure> <a href="https://bubelov.com/blog/2024/outliers/up-to-date_hu_21bc66b38693d8cf.webp"> <img src="https://bubelov.com/blog/2024/outliers/up-to-date_hu_e52d854be3b62548.webp" alt="" /> </a> </figure></p> <p>We added 55 more verified merchants, which is a good trend.</p> <p><figure> <a href="https://bubelov.com/blog/2024/outliers/total_hu_76d99525d68f9d84.webp"> <img src="https://bubelov.com/blog/2024/outliers/total_hu_b392b27a3ec57bf2.webp" alt="" /> </a> </figure></p> <p>The total number of merchants dropped by 142, mostly due to my re-verification effort. It certainly boosted our signal-to-noise ratio.</p> <p><figure> <a href="https://bubelov.com/blog/2024/outliers/days-since-verified_hu_e24be208f03c3d08.webp"> <img src="https://bubelov.com/blog/2024/outliers/days-since-verified_hu_231064943311272d.webp" alt="" /> </a> </figure></p> <p>The average number of days since the last verification dropped from 411 to 353, which is a very significant improvement. In the long run, this metric will become our key data health indicator, but we need to deal with the remaining outliers first.</p> Timestamping BTC Map https://bubelov.com/blog/2024/timestamping-btcmap/ Mon, 04 Mar 2024 00:00:00 +0000 https://bubelov.com/blog/2024/timestamping-btcmap/ <p>When we started working on <a href="https://btcmap.org">BTC Map</a>, we inherited about 8,000 places which were last verified before 2015, so we quickly figured out that adding merchants to the map is not enough, it&rsquo;s also important to make sure that our data is reliable.</p> <p>Unfortunately, OpenStreetMap doesn&rsquo;t make it easy to figure out if the data is still reliable, at least, not out of the box. There are a few optional tags which can be used to keep track of the timestamps, such as <code>check_date</code> and <code>survey:date</code>, but most Bitcoin merchant locations didn&rsquo;t have those tags, so I had to figure out the dates of the most recent full verifications and add them manually. It took me a few weeks, but now we have a full timestamp coverage, which allows us to measure the age of our data. It currently stands at ~460 days, and I hope we&rsquo;ll be able to bring it down to 365 days or even lower.</p> Measuring BTC Map Data Quality https://bubelov.com/blog/2024/btcmap-kpi/ Mon, 19 Feb 2024 00:00:00 +0000 https://bubelov.com/blog/2024/btcmap-kpi/ <p><a href="https://btcmap.org/">BTC Map</a> is our attempt to find all Bitcoin-accepting merchants worldwide. We&rsquo;ve been doing it since 2022, and I&rsquo;m pretty happy with the results so far. Here are our core KPIs:</p> <h2 id="verified-places">Verified Places</h2> <p>All the merchants are supposed to get verified from time to time. Places which were verified more than a year ago are considered outdated. The up-to-date merchants count shows us how many of our places are still reliable. Re-verifying places is a lot of work, so the more places we have, the more effort it takes to keep them up-to-date.</p> <p>There are currently 6,667 verified places, and we only had ~300 verified places when we started working on BTC Map. Keeping this metric at the current level is challenging enough, but let&rsquo;s see if we can grow the number of verified places this year.</p> <h2 id="total-places">Total Places</h2> <p>This number includes both verified and outdated places, and we currently have 10,551 places in total. It would be nice to keep all of them up-to-date, that&rsquo;s basically an upper limit of the verified places metric.</p> <h2 id="days-since-verified">Days Since Verified</h2> <p>As you noticed, the previous numbers have a one-year cutoff threshold. Days since verified is a much more sensitive metric, since it shows the average number of days since the last verification. This number is supposed to grow by one every day, unless you re-verify some places. This metric stands at ~450 days currently, and it&rsquo;s trailing up. This is concerning, since it shows that our data is getting less reliable every day.</p> <h2 id="conclusion">Conclusion</h2> <p>It&rsquo;s not enough to add Bitcoin-accepting places to the map since every place needs to be maintained and re-verified from time to time. We prefer quality to quantity, and that&rsquo;s the main reason we&rsquo;re using those time-sensitive metrics.</p> First Harvest https://bubelov.com/blog/2023/first-harvest/ Sun, 12 Nov 2023 00:00:00 +0000 https://bubelov.com/blog/2023/first-harvest/ <p><a href="https://bubelov.com/blog/2023/veggies/">Part 1</a></p> <p>Here are the first results:</p> <p><figure> <a href="https://bubelov.com/blog/2023/first-harvest/tomatoes_hu_ffa8fc6eee3611d3.webp"> <img src="https://bubelov.com/blog/2023/first-harvest/tomatoes_hu_24e1c88ff4c5694.webp" alt="" /> </a> </figure></p> <p>Unfortunately, all the cucumber plants are dead, but I&rsquo;ll keep trying.</p> <p>The key takeaways are:</p> <ul> <li>Overwatering is a thing. While plants can&rsquo;t live without water, too much of a good thing can be a bad thing. Swampy soil causes root rot, which is an untreatable and deadly disease. Tropical rains can be fatal for many plants, so it&rsquo;s better to keep them under some kind of roof.</li> <li>Fungal diseases are common in tropics, and you should use fungicide if you can&rsquo;t control the humidity level.</li> <li>Most plants don&rsquo;t mind living with extremely limited sun exposure. I have plenty of plants which get one to two hours of direct sunlight, and they&rsquo;re doing fine.</li> <li>Local plant varieties are pretty resistant to long and direct sunlight. I never saw any signs of sunburns on my vegetable plants.</li> <li>Tomato plants can easily live and produce for a few years in tropics.</li> </ul> Enemies of the Enlightenment https://bubelov.com/blog/2023/enemies-of-enlightenment/ Fri, 22 Sep 2023 00:00:00 +0000 https://bubelov.com/blog/2023/enemies-of-enlightenment/ <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#what-is-enlightenment">What is Enlightenment?</a></li> <li><a href="#joseph-de-maistre-1753-1821">Joseph de Maistre (1753-1821)</a></li> <li><a href="#johann-georg-hamann-1730-1788">Johann Georg Hamann (1730-1788)</a></li> <li><a href="#conclusion">Conclusion</a></li> <li><a href="#sources">Sources</a></li> </ul> </nav> </div> <h2 id="what-is-enlightenment">What is Enlightenment?</h2> <p>The Enlightenment is a pretty vague concept, but here are some core ideas:</p> <ul> <li>Happiness of an individual is the ultimate goal</li> <li>Science and reason are foundations of human progress</li> <li>The power of a sovereign should be limited by a constitution</li> <li>Church and state should be separated</li> </ul> <p>That sounds pretty reasonable, but there were quite a few contemporary thinkers who opposed that movement, so let&rsquo;s examine some prominent examples and see why did they oppose the Enlightenment.</p> <h2 id="joseph-de-maistre-1753-1821">Joseph de Maistre (1753-1821)</h2> <p>His life was unremarkable until the Great French Revolution, which he initially welcomed. He quickly changed his mind though, when the chaos started to spread outside France. Joseph de Maistre had a pretty combative personality, which could be one of the reasons why he was sent to work in Russia.</p> <p>The Great French Revolution promised liberty and equality, but Maistre didn&rsquo;t feel that the people became happier after the revolution. The Enlightenment promotes the view that people are rational, and they seek happiness. According to Maistre, nature is hostile and there is a constant struggle. There is nothing rational in that. Humans are natural killers, and all aspects of our lives depend on killing other species, and even our own. He was obsessed with death. He also thought that marriage is irrational, and monarchy is no different. Despite being irrational, heritable monarchy worked pretty well, while more &ldquo;enlightened&rdquo; countries such as Poland were a mess.</p> <p>Maistre believed that people value stability and reason is the worst foundation for a stable society. Reason means argument, and it leads to polarisation. Societies are based on myths and irrationality and should be forbidden to examine those foundations, since it would destroy the society. The good example is religion with its tendency to punish anyone questioning the origin stories.</p> <p>According to Maistre, custom and irrationality are foundations of our lives. We are told to be born to freedom, but why are people &ldquo;in chains&rdquo; everywhere? The last thing people desire is freedom. They need security, stability and obedience. How long did democracies last and how much did we pay for them?</p> <p>Peter the Great sent thousands of people for a sure death, and they died like sheep, with no mutiny. They had no idea why are they marching, where are they marching, and why do they need to kill. They had no hostility to their enemies. People aren&rsquo;t interested in cooperation and mutual self-help. What people really want is collective self-emulation. Wars are good examples of such tendencies.</p> <p>What must be done is to govern people in a way which prevents them from tearing each other in pieces. That&rsquo;s clearly a hyper-Hobbesian view of human nature! Maistre thought that French Revolution is a great punishment sent by God. Power must be respected because it stops societies from falling apart. Self-control is impossible, all the limits should be enforced from the outside.</p> <p>Maistre believed that strong government given in some irrational fashion is necessary. Religion and serfdom are foundations of stable society. He thought that Europe was able to abolish serfdom only because it had independent church, but Russian church was weak, so abolishing serfdom would be a huge mistake. People didn&rsquo;t respect clerics and despised the bureaucrats, so strong government was the only foundation of that society.</p> <p>According to Maistre, scientists are the ones who analyse, disintegrate and destroy. I guess it&rsquo;s partly true, so anti-science view seems less absurd when you learn about the reasons why are those people oppose an unbounded scientific push.</p> <p>Maistre also thought that no man exists without society and no society exists without sovereignty. All sovereignty implies infallibility, and infallibility rests with God. That&rsquo;s a clever attempt to insert and justify religion, I guess.</p> <p>Maistre even goes as far as saying that all power is to be worshiped. I think it&rsquo;s pretty clear that he&rsquo;s a proto-fascist.</p> <h2 id="johann-georg-hamann-1730-1788">Johann Georg Hamann (1730-1788)</h2> <p>Hamann was much more moderate than Maistre, and some people don&rsquo;t really see him as a Counter-Enlightenment thinker. Although he believed that feelings are superior to dry rationality, and all the universal rules are oppressive as they constrain human potential, he wasn&rsquo;t really opposed to most scientific endeavors. He noted that science is prone to over-generalization, abstracting away important details and exceptions, which are essential but are often lost in translation.</p> <p>Speaking of translation, Hamann believed that it&rsquo;s impossible to fully translate texts from one language to another. Since the languages are shared by people and people are unique, languages capture their unique experience which can&rsquo;t be expressed in any other language. He was generally interested in words and symbols, and he believed that reason and language is basically the same thing. To Hamann, it explained why languages aren&rsquo;t that coherent and logical, they just mirror the human nature, after all.</p> <p>Politically, Hamann was more of a conservative. He believed that our ancestors knew that they were doing, and anything old, anything ancient, exists for a reason and must be preserved. He also suspected that the goal of the Enlightenment is to organize an enlightened despotism. In technical terms, he didn&rsquo;t like the idea of imposing a unified schema on a global scale. It&rsquo;s easy to write down a bunch of rules, but it doesn&rsquo;t mean those rules can be actually lived and followed. He much preferred natural law, and he believed that any amends imposed by the Enlightenment might be harmful and contradictory to human nature. Natural law is basically anything which any reasonable being perceives to be true, which doesn&rsquo;t leave much space for a big government and any far-reaching top-down reforms.</p> <p>Hamann doesn&rsquo;t strike me as an enemy of rationality, he was only cautious about going too far with abstracting away the reality. He believed that all states are rational and are founded on natural law and social contract. Keeping your promises is also kind of natural, so it can be reduced to a natural law alone. If we accept that every state is rational, it would be hard to deny that any state which suppresses rationality is simply self-destructive. That line of thinking leads to a strong anti-censorship position, which Hamann indeed shared with many Enlightenment thinkers. He was a devout Christian, but he believed that all religions are rational, and forcing religious unity is wrong and dangerous.</p> <p>Like Maistre, Hamann wasn&rsquo;t a fan of trying to justify and rationalize the existence of the state. He thought that justifying the state is as weird as justifying birds, they don&rsquo;t need justification to exist since they&rsquo;re just a part of natural world.</p> <p>Some people view Hamann as proto-fascist, but I have an impression that he&rsquo;s more of an anarchist. He clearly doesn&rsquo;t like overreaching entities which can concentrate power and use it to force their will on others. He also criticized the idea of specialization, which can alienate people from nature and make them fully dependent on centralized power structures.</p> <h2 id="conclusion">Conclusion</h2> <p>I believe it&rsquo;s important to constantly doubt and test any idea, and the Enlightenment is no exception. Nothing good is beyond criticism, and both of the thinkers listed above formulated a good set of arguments. I don&rsquo;t really buy most of those arguments though, but it&rsquo;s hard to deny that they were extremely influential, and they contributed to the rise of both fascism and anarchism.</p> <h2 id="sources">Sources</h2> <ul> <li>Enemies of the Enlightenment (Lecture by Isaiah Berlin, 1965)</li> <li>Wikipedia</li> </ul> AirGradient Open Air Review https://bubelov.com/blog/2023/airgradient-open-air/ Sat, 02 Sep 2023 00:00:00 +0000 https://bubelov.com/blog/2023/airgradient-open-air/ <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#disclaimer">Disclaimer</a></li> <li><a href="#indoor-air-is-a-priority">Indoor Air is a Priority</a> <ul> <li><a href="#how-to-reduce-co2">How to Reduce CO2</a></li> <li><a href="#how-to-reduce-pm-25">How to Reduce PM 2.5</a></li> <li><a href="#outdoor-air-monitoring">Outdoor Air Monitoring</a></li> </ul> </li> <li><a href="#airgradient-open-air">AirGradient Open Air</a> <ul> <li><a href="#unboxing">Unboxing</a></li> <li><a href="#diy-vs-pre-assembled">DIY vs Pre-Assembled</a></li> <li><a href="#assembling-the-diy-model">Assembling the DIY Model</a></li> <li><a href="#mounting">Mounting</a></li> <li><a href="#software-setup">Software Setup</a></li> <li><a href="#going-cloudless">Going Cloudless</a></li> <li><a href="#what-does-open-source-mean-exactly">What Does Open Source Mean, Exactly?</a></li> <li><a href="#accuracy">Accuracy</a></li> <li><a href="#components-case">Components: Case</a></li> <li><a href="#components-board">Components: Board</a></li> <li><a href="#components-sensors">Components: Sensors</a></li> <li><a href="#components-external-hardware-watchdog">Components: External Hardware Watchdog</a></li> <li><a href="#notes-on-power-management">Notes on Power Management</a></li> </ul> </li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="disclaimer">Disclaimer</h2> <p>After <a href="https://bubelov.com/blog/2023/airgradient-pro-kit-review/">reviewing</a> AirGradient Pro Kit, I was contacted by AirGradient, and they offered me two Open Air units for review. I was planning to buy and review an Open Air unit anyway, so I don&rsquo;t think it biased my review, but I think it should be noted nevertheless.</p> <h2 id="indoor-air-is-a-priority">Indoor Air is a Priority</h2> <p>People spend most of their time indoors, and exposure time matters, which means that your bedroom is a perfect place to start your air quality improvement journey. I placed my first AirGradient Pro unit in my bedroom, and it helped me identify the following issues:</p> <ol> <li>CO2 concentration often reached 2,300 ppm by the early morning, which is considered unhealthy. Let me cite <a href="https://www.canada.ca/en/health-canada/services/publications/healthy-living/residential-indoor-air-quality-guidelines-carbon-dioxide.html">Canadian regulations</a> as an example:</li> </ol> <blockquote> <p>The recommended long-term exposure limit for CO2 is 1,000 ppm (based on a 24-hour average). The guidelines are based on effects observed in epidemiological studies in schools or offices and controlled exposure studies.</p> </blockquote> <ol start="2"> <li>PM 2.5 often exceeds 5 μg/m3, which is a <a href="https://www.who.int/publications/i/item/9789240034228">WHO safe air threshold</a>. Those spikes often coincide with ventilation, which hints that the outdoor air isn&rsquo;t always as clean as I thought.</li> </ol> <blockquote> <p>The data obtained for cause-specific mortality also support a long-term PM 2.5 AQG (Air Quality Guidelines) level of no more than 5 μg/m3.</p> </blockquote> <h3 id="how-to-reduce-co2">How to Reduce CO2</h3> <p>There are a few things which increase indoor CO2 concentration:</p> <ul> <li>Human and animal breathing</li> <li>Gas-powered kitchen appliances</li> <li>Gas-powered heating</li> </ul> <p>I don&rsquo;t have any gas appliances in my house, but my bedroom needs to accommodate two persons and a single dog every night. Sleeping alone would certainly help, but it would be nice to find more realistic and universal solution. That solution is ventilation.</p> <p>Before the Industrial Revolution, the outdoor air contained about 280 ppm of carbon dioxide. Our material well-being has reached unbelievable heights since then, but most of those improvements are based on cheap energy extraction, which happens to produce a lot of carbon dioxide. As a result, the current atmospheric CO2 level is already approaching 420 ppm.</p> <p>The good news is, 420 ppm is still safe for breathing, so you can simply let some outdoor air in if you want to reduce your indoor CO2 concentration. Targeting 420 ppm is rarely a good idea, since ventilation involves heat transfer, which adds more work for your AC or a heating system. I&rsquo;d say anything below 750-1,000 ppm is probably fine.</p> <h3 id="how-to-reduce-pm-25">How to Reduce PM 2.5</h3> <p>As far as I understand, high PM 2.5 concentration is much more dangerous than high CO2 level, so I would probably choose being exposed to 2,000 ppm of CO2 over 50 ug/m3 of PM 2.5. Unfortunately, Thais often burn their garbage on the spot instead of utilizing it properly, and I&rsquo;m not really in a position to change this culture. I often smell burning plastics and other nasty stuff when I&rsquo;m walking my dog or tending my garden. Obviously, I don&rsquo;t want to let any air into my house during those periods. This air is toxic, and it smells bad, too.</p> <p>Given the facts above, it&rsquo;s pretty clear that ventilation is necessary, but you should only ventilate your house when the outdoor air is safe and clean. I guess it&rsquo;s OK to let some slightly dirty air in, if you don&rsquo;t have a choice and if you have an air filter, but filters aren&rsquo;t cheap, and they can&rsquo;t fully eradicate bad odors. The best filters are also pretty noisy, since pushing the air through a good filter is hard, so it&rsquo;s better to avoid this step or at least to minimize the working time of your air treatment system.</p> <h3 id="outdoor-air-monitoring">Outdoor Air Monitoring</h3> <p>As we figured out earlier, ventilation can only improve your indoor air quality if the outdoor air is actually cleaner, but how do you know if it&rsquo;s safe to ventilate? That&rsquo;s the main issue I&rsquo;m trying to solve and getting an outdoor air monitor seems like a good solution. CO2 and PM 2.5 aren&rsquo;t the only things I&rsquo;m interested in, I&rsquo;d like to keep track of as much other metrics as possible. I&rsquo;m using self-hosted Home Assistant as a backend, and it makes it easy to keep and review all the historical data.</p> <h2 id="airgradient-open-air">AirGradient Open Air</h2> <p>As I mentioned earlier, I&rsquo;ve got two units, one of them being <a href="https://www.airgradient.com/open-air/">pre-assembled</a> and another one is <a href="https://www.airgradient.com/open-air/">more of a DIY type</a>. The DIY kit doesn&rsquo;t require any soldering, which is good, since I don&rsquo;t really know how to solder. Buying a <a href="https://www.pine64.org/pinecil/">Pinecil</a> is on my TODO list, but I&rsquo;m not sure when I&rsquo;ll have time to pick up this valuable skill.</p> <h3 id="unboxing">Unboxing</h3> <p>The box itself is unremarkable, it contains the picture of a product, a brief summary of company&rsquo;s goals and priorities and some contact details which can come handy if you need any assistance of if you have questions about the product.</p> <p><figure> <a href="https://bubelov.com/blog/2023/airgradient-open-air/box-front_hu_5bf4c8de918e4ca9.webp"> <img src="https://bubelov.com/blog/2023/airgradient-open-air/box-front_hu_c846b5efa2c9241.webp" alt="" /> </a> <figcaption>Front side of the box</figcaption> </figure></p> <p><figure> <a href="https://bubelov.com/blog/2023/airgradient-open-air/box-back_hu_32f33492f2c85794.webp"> <img src="https://bubelov.com/blog/2023/airgradient-open-air/box-back_hu_d5a2c4982bbe5b06.webp" alt="" /> </a> <figcaption>Back side of the box</figcaption> </figure></p> <p>For a pre-assembled version, the box itself only contains an Open Air unit and a 4 meter-long USB cable:</p> <p><figure> <a href="https://bubelov.com/blog/2023/airgradient-open-air/assembled_hu_22f687f9b63aa16e.webp"> <img src="https://bubelov.com/blog/2023/airgradient-open-air/assembled_hu_2b2d4710c794c2ad.webp" alt="" /> </a> <figcaption>Assembled AirGradient Open Air</figcaption> </figure></p> <h3 id="diy-vs-pre-assembled">DIY vs Pre-Assembled</h3> <p>The DIY kit looks almost identical to a pre-assembled unit, and it&rsquo;s made from the same components. I&rsquo;m pretty sure that Plantower has a sensible QA process, and it&rsquo;s extremely unlikely to get a sensor which does not work at all, but AirGradient also has their own air chamber which they&rsquo;re using to test the pre-assembled units, just to add an extra level of confidence.</p> <p>Here are the main distinctive features of the pre-assembled model:</p> <ol> <li>Every pre-assembled uint is tested by AirGradient, and you can always fetch your test report from your AirGradient dashboard.</li> <li>There is a 1-year warranty period.</li> <li>The serial number is etched on the back side of the case, which makes it slightly more convenient to perform an initial setup.</li> </ol> <p>By the way, here is a test report which I got for my pre-assembled model:</p> <p><figure> <a href="https://bubelov.com/blog/2023/airgradient-open-air/test-report_hu_4bde4efafd538520.webp"> <img src="https://bubelov.com/blog/2023/airgradient-open-air/test-report_hu_48b1faa8ab39607c.webp" alt="" /> </a> <figcaption>Test report</figcaption> </figure></p> <p>I contacted AirGradient for more details, and they claimed that about 3% of Plantower sensors fail their tests, but it&rsquo;s only because those tests require 5 μg/m3 precision while the manufacturer only promises 10 μg/m3 precision. It looks like Plantower is very conservative in their claims regarding their sensor accuracy and most of their sensors are much more precise than stated in the specs.</p> <h3 id="assembling-the-diy-model">Assembling the DIY Model</h3> <p>There are a few misc tools to connect all the pieces together:</p> <p><figure> <a href="https://bubelov.com/blog/2023/airgradient-open-air/tools_hu_add6440160c83011.webp"> <img src="https://bubelov.com/blog/2023/airgradient-open-air/tools_hu_bae29c60493207d1.webp" alt="" /> </a> <figcaption>Tools</figcaption> </figure></p> <p>The heart of this device is two identical Plantower sensors:</p> <p><figure> <a href="https://bubelov.com/blog/2023/airgradient-open-air/sensors_hu_1760d4d55f6d24bc.webp"> <img src="https://bubelov.com/blog/2023/airgradient-open-air/sensors_hu_d79da0c1f21a5d44.webp" alt="" /> </a> <figcaption>Sensors</figcaption> </figure></p> <p>It&rsquo;s pretty easy to connect those sensors to the main board:</p> <p><figure> <a href="https://bubelov.com/blog/2023/airgradient-open-air/sensors-wired_hu_216b06e6e1a8a724.webp"> <img src="https://bubelov.com/blog/2023/airgradient-open-air/sensors-wired_hu_59f7044e8109611a.webp" alt="" /> </a> <figcaption>Sensors with wires</figcaption> </figure></p> <p><figure> <a href="https://bubelov.com/blog/2023/airgradient-open-air/sensors-connected_hu_740b81843c1e0983.webp"> <img src="https://bubelov.com/blog/2023/airgradient-open-air/sensors-connected_hu_4f54c666015cdd3a.webp" alt="" /> </a> <figcaption>Sensors connected to the board</figcaption> </figure></p> <p><figure> <a href="https://bubelov.com/blog/2023/airgradient-open-air/board-connected_hu_80ea2f800addb79.webp"> <img src="https://bubelov.com/blog/2023/airgradient-open-air/board-connected_hu_6508194c2d0786e8.webp" alt="" /> </a> <figcaption>Sensors connected to the board</figcaption> </figure></p> <p>The case consists of two parts, and they fit together nicely:</p> <p><figure> <a href="https://bubelov.com/blog/2023/airgradient-open-air/case-parts_hu_150a2ff1cd74c6c.webp"> <img src="https://bubelov.com/blog/2023/airgradient-open-air/case-parts_hu_130bfc262c5e4cbf.webp" alt="" /> </a> <figcaption>Case parts</figcaption> </figure></p> <p><figure> <a href="https://bubelov.com/blog/2023/airgradient-open-air/case-connected_hu_f544af3ceb16842e.webp"> <img src="https://bubelov.com/blog/2023/airgradient-open-air/case-connected_hu_a1d1c628ab160a1d.webp" alt="" /> </a> <figcaption>Grill with all the components connected</figcaption> </figure></p> <p><figure> <a href="https://bubelov.com/blog/2023/airgradient-open-air/assembled_hu_22f687f9b63aa16e.webp"> <img src="https://bubelov.com/blog/2023/airgradient-open-air/assembled_hu_2b2d4710c794c2ad.webp" alt="" /> </a> <figcaption>Assembled AirGradient Open Air</figcaption> </figure></p> <p>Assembling AirGradient Open Air is pretty easy, but you need to be careful when connecting the sensor wires, because they are quite thin, and it&rsquo;s easy to damage them by applying more pressure that they can handle. I tried not to touch them and press on the plastic area around them in order to connect those wire plugs to their corresponding sockets.</p> <h3 id="mounting">Mounting</h3> <p>Mounting is the most complicated part of AirGradient Open Air setup, and there are two recommended methods:</p> <ol> <li>Mounting on a pole</li> <li>Mounting on a wall</li> </ol> <p>I guess there aren&rsquo;t many people who have a spare pole which also happens to be equipped with a power socket, so wall mount seems to be the only way for most of us. Basically, you need to find a place nearby an external power socket, and you might want to aim high, since hanging your sensors too close to the ground can expose them to the amounts of dust which are only present on the ground level, and it might not represent the air you inhale or feed to your ventilation system.</p> <p>Air quality can depend on the height at which you sample your outdoor air, and I think this problem can be approached from two angles:</p> <ol> <li>Put your sensors where your nose is, just to be certain that the data best represents what you actually inhale. I can&rsquo;t back this claim with any science, but is seems like the safe bet.</li> <li>If you have an automatic ventilation system, it might make sense to hang your sensors somewhere close to your inlet vent holes, just to make sure that the readings will represent the air which goes into your home.</li> </ol> <p>I have a drill, but I only used it once, when I needed to hang a mailbox. I have a cheap wired model and almost no experience, so drilling two holes 3.5 meters high wasn&rsquo;t exactly an easy job for me. To complicate matters, I knew that there is a hidden powerline going through the same spot, and I certainly didn&rsquo;t want to damage it.</p> <p><figure> <a href="https://bubelov.com/blog/2023/airgradient-open-air/mount-tools_hu_36b8e25c8c3c589e.webp"> <img src="https://bubelov.com/blog/2023/airgradient-open-air/mount-tools_hu_2696c8f13d9b266b.webp" alt="" /> </a> <figcaption>Mounting tools</figcaption> </figure></p> <p><figure> <a href="https://bubelov.com/blog/2023/airgradient-open-air/width_hu_b76b97e35e8ad957.webp"> <img src="https://bubelov.com/blog/2023/airgradient-open-air/width_hu_1d012202e22deacb.webp" alt="" /> </a> <figcaption>Width</figcaption> </figure></p> <p><figure> <a href="https://bubelov.com/blog/2023/airgradient-open-air/height_hu_f948ebceb79dba75.webp"> <img src="https://bubelov.com/blog/2023/airgradient-open-air/height_hu_b29e549f8e30d9f6.webp" alt="" /> </a> <figcaption>Height</figcaption> </figure></p> <p>I choose a 10 cm padding from the roof and the wall on the right. I was pretty anxious about the powerline, so I turned off the corresponding switches and checked the wires with a non-invasive voltage detector. Also, I paused the drilling process a few times just to make sure there is no plastic fragments in the holes or among the dust. Needless to say, you should also make sure that the ladder is stable, and you should wear some protective equipment such as a dust mask and a pair of glasses.</p> <p><figure> <a href="https://bubelov.com/blog/2023/airgradient-open-air/mounting-point_hu_5e1a48899785aaf1.webp"> <img src="https://bubelov.com/blog/2023/airgradient-open-air/mounting-point_hu_80e9ac926074460c.webp" alt="" /> </a> <figcaption>Mounting point</figcaption> </figure></p> <p>I settled on this point because I have an outdoor power socket nearby, but I surely don&rsquo;t like the aesthetics of an exposed USB cable and I will probably drill the roof and hide the cable there. The other option was to install it in a front yard, but there is a risk that a proximity to my car and the road can cause short term spikes which aren&rsquo;t really actionable and I don&rsquo;t see any good reasons to track them.</p> <p><figure> <a href="https://bubelov.com/blog/2023/airgradient-open-air/mounted_hu_81999c8d32613754.webp"> <img src="https://bubelov.com/blog/2023/airgradient-open-air/mounted_hu_7b49da81e39543b7.webp" alt="" /> </a> <figcaption>Mounted unit</figcaption> </figure></p> <h3 id="software-setup">Software Setup</h3> <p>Setting up AirGradient products can be a bit tricky, here is a high level overview:</p> <ol> <li>You need to prepare your smartphone and make sure that your router is prepared to handle 2.4 GHz connections. Most routers default to 5 GHz because it&rsquo;s much faster, but that extra speed comes at a cost of slightly reduced range and stability. I believe that&rsquo;s the reason why most smart home devices prefer 2.4 GHz, but cost and availability might also be a factor there. Most Espressif microcontrollers aren&rsquo;t 5 GHz capable, although there are a few new models which support both 2.4 GHz and 5 GHz frequencies.</li> <li>After powering on your device, you&rsquo;ll have 90 seconds to find and join its Wi-Fi network. After joining that network, you&rsquo;ll be redirected to a device Wi-Fi setup page, where you should type your 2.4 GHz home Wi-Fi network name and password. After that, your new AirGradient device will be able to connect to a cloud dashboard (which is completely optional but more on that later).</li> <li>AirGradient Open Air doesn&rsquo;t have a display, so it&rsquo;s not usable without a cloud dashboard or some other backend. You can use your own backend to store the sensor readings, but the easiest way to get started is to create an AirGradient cloud account and to add your device to the web dashboard. You&rsquo;ll need to type your device serial number, which is etched on the back side of the unit.</li> </ol> <h3 id="going-cloudless">Going Cloudless</h3> <p>AirGradient hardware and software are fully open source and there is no vendor lock-in. The device is based on <a href="https://www.espressif.com/sites/default/files/documentation/esp32-c3-mini-1_datasheet_en.pdf">ESP32-C3-MINI-1</a>, which can be fully reprogrammed with Arduino or ESP Home. You can also just use GCC to build a binary and flash it with the open source <a href="https://github.com/espressif/esptool">esptool</a>.</p> <h3 id="what-does-open-source-mean-exactly">What Does Open Source Mean, Exactly?</h3> <p>When it comes to air quality sensors, AirGradient is the only open source software and hardware company I&rsquo;m aware of, but I noticed that many people don&rsquo;t really understand what it means in an IoT context.</p> <p>Smart home appliances are actually pretty complex, and they have many components sourced from different vendors. When an IoT company calls its software open source, they usually mean the code <strong>they</strong> upload to the microcontroller. There are many microcontroller subsystems and external parts which may run their own software and that software is unlikely to be open source.</p> <p>In case of AirGradient, their firmware is <a href="https://github.com/airgradienthq/arduino">available on GitHub</a>, and it contains pretty much everything which <strong>they</strong> instruct their hardware to do. It doesn&rsquo;t cover a Wi-Fi stack or a USB stack, for example, because it&rsquo;s done by the microcontroller vendor (Espressif) and there is no way to see how it works, as far as I&rsquo;m aware. I think Espressif has an open source SDK, and its Arduino core is open source, too, but there are always some lower-level subsystems which tend to be run by unverifiable code blobs.</p> <p>Same goes to many external modules, such as Plantower PMS5003T, which is used in Open Air products. Those sensor modules have their own firmware, and it can be very hard to figure out that it actually does. For instance, you can&rsquo;t really assume how a light scatter measurement becomes a μg/m3 value which goes to the microcontroller. Some modules may hold a bunch of historical measurements and only report the average values. Fully open source software is currently a pipe dream, but not adding any more closed code on top of that mess is a good start.</p> <p>Open source hardware is also a tricky term. The hardware is not really open source all the way down to the atomic level, because it&rsquo;s impossible to find fully transparent components with every physical piece being documented and shared with the public. Open source hardware just means that the way those components are wired is well known and easily reproducible. If you&rsquo;re a software engineer like me, your can imagine it as a dependency graph, where some of your libs may depend on other lower-level libs, which themselves depend on some weird pre-compiled blobs, but everything seems to work as promised so no one takes an issue with that.</p> <p>Does it mean that open source is just a deceptive marketing trick? No, and there are two main reasons:</p> <ol> <li>Open source firmware makes it easier to debug your devices or point them to your own server, breaking the vendor lock. It also guarantees that AirGradient is not spying on you.</li> <li>Open source hardware design makes it easier to replicate the device and that&rsquo;s the only protection you have from being ripped off in case the company decides to raise their prices to an unreasonable level. The ease of replication is the best guarantee of sensible pricing.</li> <li>Open source hardware tends to be easier to repair.</li> <li>It&rsquo;s usually much easier to modify and extend open source hardware. Coupled with open source and equally modifiable firmware, it gives you the ultimate flexibility and a sense of being in control of your stuff.</li> </ol> <p>Another important thing which must be noted is licensing. It&rsquo;s not impossible to reverse engineer many closed IoT devices, but it&rsquo;s still a waste of your time, and the manufacturers can always sue you if you try to copy their products. Open source licences tend to be very liberal about what you can do with the source code and hardware schematics alike.</p> <h3 id="accuracy">Accuracy</h3> <p>Obviously, this device is as accurate as its sensors, and the only sensors on board are two identical <a href="https://www.plantower.com/en/products_33/74.html">Plantower PMS5003T</a>.</p> <p>Particle Maximum Consistency Error (PM2.5 standard data):</p> <blockquote> <p>±10%@100~500µg/m3</p> </blockquote> <blockquote> <p>±10µg/m3@0~100µg</p> </blockquote> <p>Which raises two questions:</p> <ol> <li>Is it true?</li> <li>If it&rsquo;s true, is it good enough?</li> </ol> <p>The first question can be answered by looking for tests and tear-downs online, here is a few interesting links:</p> <ul> <li><a href="https://www.nature.com/articles/s41598-019-43716-3">Nature: Long-term field comparison of multiple low-cost particulate matter sensors in an outdoor urban environment</a></li> <li><a href="https://goughlui.com/2021/03/14/review-teardown-plantower-pms5003-laser-particulate-monitor-sensor/">Review, Teardown: Plantower PMS5003 Laser Particulate Monitor Sensor</a></li> <li><a href="https://aqicn.org/sensor/pms5003-7003/hk/">The Plantower PMS5003 and PMS7003 Air Quality Sensor experiment</a></li> </ul> <p>In short, it seems like Plantower, as well as many other low cost PM 2.5 sensors, is pretty accurate. Although low cost sensors might disagree within a single digit range, but they can quickly capture any spikes in PM concentration, so there is no lag, and you can act on that data right away.</p> <p>In my view, the stated accuracy is good enough, but you should understand that the manufacturer is basically saying that the sensor can report somewhat healthy 5 µg/m3 even if the real PM concentration is pretty unhealthy (up to 15 µg/m3). That&rsquo;s an extreme example, but it&rsquo;s the worst case you should know about. Personally, it&rsquo;s not a big deal for me, because I&rsquo;m interested in detecting much more dangerous PM concentrations (20-50 µg/m3 and more), and this sensor can do that if a fast and reliable fashion.</p> <p><figure> <a href="https://bubelov.com/blog/2023/airgradient-open-air/dashboard_hu_ea63b024c43ed7b2.webp"> <img src="https://bubelov.com/blog/2023/airgradient-open-air/dashboard_hu_73111c6bf6c76074.webp" alt="" /> </a> <figcaption>Dashboard</figcaption> </figure></p> <p>I also asked AirGradient if they have any data on the accuracy of the components used in Open Air production, and they told me that they run a <a href="https://www.airgradient.com/research/">global co-location test</a> with the aim of completely understanding the performance of Open Air units. They maintain a list of their current research partners, and this list includes many of the world&rsquo;s top educational institutions.</p> <h3 id="components-case">Components: Case</h3> <p>The case is really nice, and I like it very much. Everything fits well, and the plastic is nice and smooth. This kind of plastic is UV resistant, so it shouldn&rsquo;t get brittle and change it&rsquo;s color from white to yellow in a few months.</p> <p>You can also 3D print your own case if your damage the original one or if you just don&rsquo;t like it for whatever reason. Unfortunately, I don&rsquo;t have a 3D printer, so I can&rsquo;t really experiment with my own enclosure designs. A friend of mine got a $500 3D printer, and I&rsquo;m impressed with what it&rsquo;s capable of, so maybe I&rsquo;ll try it too.</p> <h3 id="components-board">Components: Board</h3> <p>Open Air board is a radical departure from a design approach used in an indoor Pro model. The Pro model basically has two boards, and only one of them is made by AirGradient:</p> <p><figure> <a href="https://bubelov.com/blog/2023/airgradient-open-air/pro_hu_ecc0e592c2cadeb4.webp"> <img src="https://bubelov.com/blog/2023/airgradient-open-air/pro_hu_86ebd5521dd5dae.webp" alt="" /> </a> </figure></p> <p>As you can see, the big AirGradient Pro board has a slot for a <a href="https://www.wemos.cc/en/latest/d1/d1_mini.html">LOLIN D1 mini</a> Espressif WiFi board. AirGradient Open Air uses a much smaller board with an SMT-mounted <a href="https://www.espressif.com/sites/default/files/documentation/esp32-c3-mini-1_datasheet_en.pdf">ESP32-C3-MINI-1</a> microcontroller:</p> <p><figure> <a href="https://bubelov.com/blog/2023/airgradient-open-air/board_hu_4385ee79cc278205.webp"> <img src="https://bubelov.com/blog/2023/airgradient-open-air/board_hu_9f1a531cfeae0fb4.webp" alt="" /> </a> </figure></p> <p>This board looks much more polished, but SMT-mounted components also have some downsides, such as reduced modularity. I really like the fact that I can take off that D1 mini and use is as a general purpose dev board in any other project. I also have many spare D1 mini boards, so I can easily replace the board if it fails.</p> <p>Although Open Air boards are less common, they are <a href="https://www.airgradient.com/shop/">available</a> in the AirGradient online shop, and they can also be used as general purpose dev boards. I guess I&rsquo;m okay with that, since AirGradient boards are open source, easy to use and affordable. In fact, they offer slightly better initial setup experience for Arduino users, because they work out of the box, unlike some common dev boards such as C3 mini, which <a href="https://www.wemos.cc/en/latest/tutorials/c3/get_started_with_arduino_c3.html">requires</a> some weird button press combos and re-powering the board after the first reflash.</p> <p>The only thing I miss is board auto-detection when working with Arduino. Unfortunately, it&rsquo;s a hardware issue, since Arduino board detection is based on USB vendor ID (VID) and USB product ID (PID) combination, which aren&rsquo;t always re-programmable.</p> <p>Here is a part of <code>lsusb</code> dump:</p> <blockquote> <p>idVendor: 0x303a</p> <p>idProduct: 0x1001</p> </blockquote> <p>You can check this <a href="https://github.com/espressif/usb-pids">Espressif repo</a> to figure out what those numbers mean. In short, <code>0x303a</code> is USB vendor ID which belongs to Espressif, and <code>0x1001</code> is a default value which C3 controllers are unable to change. As a result, AirGradient board won&rsquo;t be recognized by Arduino and there is no way to recognize it automatically, which means that you need to select the board manually before flashing new firmware. This issue affects all C3 boards and there is nothing special about AirGradient Open Air board, but I hope Espressif or any other manufacturer will make their future chips more flexible in that regard.</p> <h3 id="components-sensors">Components: Sensors</h3> <p>AirGradient Open Air has two identical Plantower sensors. It can also work with a single sensor, but having two sensors allows the firmware to average out the readings, which is supposed to reduce volatility. It also allows the firmware to perform some sanity checks since those sensors are supposed to produce similar readings. If there is a significant disagreement, it&rsquo;s a clear indication of a hardware failure.</p> <h3 id="components-external-hardware-watchdog">Components: External Hardware Watchdog</h3> <p>The inclusion of <a href="https://www.ti.com/product/TPL5110">Texas Instruments TPL5010</a> is probably the most mysterious part of Open Air board. I don&rsquo;t know much about that chip, but it looks like an external timer which is used to improve power efficiency. Most IoT devices don&rsquo;t really need to be awake all the time, so they can power off and let the external timer wake them up when necessary. Many microcontrollers can disable most of their subsystems when being asked nicely, so you can avoid using a separate timer chip, but external timers aren&rsquo;t that uncommon.</p> <p>The website doesn&rsquo;t say much about the purpose of that timer:</p> <blockquote> <p>We integrated an external hardware watchdog chip that automatically reboots the unit in case something goes wrong and thus the monitor can achieve high uptimes.</p> </blockquote> <p>So I decided to ask AirGradient and here is the reply I got:</p> <blockquote> <p>It could be that voltage fluctuations crash the MCU or potentially memory leaks etc. Basically, on each successful data transmission to the server the watchdog is reset. If for whatever reason the transmission is not happening, the device will reboot. Before we used it we had sometimes monitors getting stuck after a few weeks and needed a reboot. The watchdog basically led to monitors running for years without any intervention needed.</p> </blockquote> <p>It looks like this chip is solving a real problem, but it has nothing to do with power efficiency. Outdoor units can be mounted in pretty inconvenient places, so minimizing any manual interventions seems like a good priority.</p> <h3 id="notes-on-power-management">Notes on Power Management</h3> <p>AirGradient Open Air requires a power socket, which is a serious pain point, and it begs the following questions:</p> <ol> <li>Would it be better to power it from batteries, like a TV remote?</li> <li>Can&rsquo;t it be equipped with a solar panel and a rechargeable battery?</li> </ol> <p>Some devices, such as TV remotes, are supposed to be powered by a couple of AA or AAA batteries, which are usually non-rechargeable, and they can last for a few years. Those devices spend most of their time sleeping, so their power draw is extremely low. Rechargeable batteries may sound like a good idea, but they have a natural rate or discharge of about 10% a month, which would limit the device lifetime from a single charge. AirGradient Open Air is not a low power device as it draws hundreds of mA during transmissions, so it won&rsquo;t even last a month when powered by batteries.</p> <p>More power-hungry devices such as smartphones or power tools are expected to drain their batteries many times a year, which makes it impractical to use non-rechargeable batteries. When you expect your battery to drain in a few hours of days, you&rsquo;ll probably won&rsquo;t even notice that it loses a tenth of its charge a month.</p> <p>I think it&rsquo;s possible to reduce the power draw of AirGradient Open Air, but it would still require a pretty massive rechargeable battery. Solar panel output depends on environmental factors and there is too much uncertainty in using them as the only power source. Needless to say that both of those components add to costs and complexity, so I&rsquo;m not convinced that it would be a good idea to make a &ldquo;wireless&rdquo; version.</p> <p>There is also some evidence that Plantower sensors work best when they&rsquo;re powered on all the time, so it would be risky to use any aggressive optimizations without some serious testing.</p> <h2 id="conclusion">Conclusion</h2> <p>AirGradient Open Air is the only open source air quality sensor I&rsquo;m aware of, so there is nothing to compare it to. Although the mounting is a bit tricky, the device itself is accurate and very affordable.</p> <p>Open Air is aggressively optimized for reliability and as little maintenance as possible, which are sensible priorities. It&rsquo;s clear that the company learned a lot since the release of the indoor model, and it shows. The design and quality of the case are really impressive, and I like the fact that they&rsquo;re sharing their experience and findings with the public via their <a href="https://www.airgradient.com/blog/">corporate blog</a>. I was always interested in how you can get from an idea to a mass-produced product, and I&rsquo;ve learned a ton from AirGradient website and a community forum.</p> <p>Pros:</p> <ul> <li>Open source</li> <li>Cheap</li> <li>Repairable</li> <li>Cloud is optional, so there is no lock-in</li> <li>Can be easily integrated with Home Assistant, which plays well with Espressif hardware</li> <li>Extremely resilient</li> <li>High quality case made from 2.5 mm ASA plastic</li> <li>Great support and community</li> </ul> <p>Cons:</p> <ul> <li>Mounting is hard</li> <li>The repo is a bit messy</li> <li>There no mobile app which would make onboarding and device management easier for non-technical users</li> <li>While the device itself has a USB type C port, the other side of the included cable has an old style type A plug. There are plenty of wall adapters with type C ports and I think that type A shouldn&rsquo;t be used in any new devices.</li> </ul> French Press and Beyond https://bubelov.com/blog/2023/french-press/ Sun, 20 Aug 2023 00:00:00 +0000 https://bubelov.com/blog/2023/french-press/ <p>I&rsquo;ve used a French press for years, it&rsquo;s cheap, simple, and makes a solid cup of coffee. Honestly, it&rsquo;s the easiest way to brew something decent. But lately, I’m getting a little bored with the same style, so I&rsquo;m thinking about trying something new.</p> <p>Espresso is the holy grail of coffee. It&rsquo;s what most cafes sell, since drinks like cappuccino, latte, and americano are all basically just espresso in disguise.</p> <p>The problem is, espresso is tricky to make. You need expensive, bulky equipment, which makes it pretty impractical for home brewing.</p> <p>At least, that&rsquo;s what I thought, until someone showed me manual espresso makers like Flair. Turns out you can actually make café-quality espresso at home with one of these simple, affordable gadgets.</p> <p>I just ordered one, so I&rsquo;ll be back soon with how it goes.</p> Manual Coffee Grinders https://bubelov.com/blog/2023/grinders/ Thu, 10 Aug 2023 00:00:00 +0000 https://bubelov.com/blog/2023/grinders/ <p>I a coffee addict, but I never really knew how it’s made. For most of my life, I just drank instant coffee, it was cheap and convenient. Where I grew up, cafes were rare and kind of expensive. Old habits die hard, I guess.</p> <p>But I developed a taste for real coffee when I moved to Moscow in 2013. Once you get used to the good stuff, there&rsquo;s no going back to instant.</p> <p>I&rsquo;ve been drinking coffee in cafes since then, and I eventually bought a French Press to make an extra cup or two at home. Having a French Press meant I had to learn a few basics, like how coffee beans need to be roasted and ground before you can brew with one. Luckily, the local Starbucks would grind the beans right when I bought them, and I found some pretty decent pre-ground beans in vacuum packs at Ikea.</p> <p>As my coffee obsession grew, I noticed that not all beans are the same. They lose their flavor and kick pretty quickly, especially if they&rsquo;ve already been ground. So the key to good coffee is freshness, use the freshest beans you can find, and grind them right before you brew.</p> <p>So you can&rsquo;t really make good coffee without a grinder, and there are plenty of them out there. For a French press, any decent grinder costing $20 or more should do. But if you want to grind for espresso or Turkish coffee, you&rsquo;ll probably need to spend at least $200.</p> <p>I want to try different styles of coffee, so I opted for the pricier option and ordered <a href="https://1zpresso.coffee/k-ultra/">1Zpresso K-Ultra</a>. I&rsquo;ve been using it for a few days and I really like the results. Truth to be told, I would probably be okay with a slightly cheaper grinder from any other brand, but I really like the design and ergonomics of K-Ultra:</p> <p><figure> <a href="https://bubelov.com/blog/2023/grinders/k-ultra-9_hu_3e00debb5e707135.webp"> <img src="https://bubelov.com/blog/2023/grinders/k-ultra-9_hu_bf6366a68135db28.webp" alt="" /> </a> </figure></p> King Coal https://bubelov.com/blog/2023/king-coal/ Mon, 07 Aug 2023 00:00:00 +0000 https://bubelov.com/blog/2023/king-coal/ <p><a href="https://standardebooks.org/ebooks/upton-sinclair/king-coal">https://standardebooks.org/ebooks/upton-sinclair/king-coal</a></p> <p>King Coal is the first book by Upton Sinclair that I&rsquo;ve read, and it won&rsquo;t be the last. It&rsquo;s a great example of socialist propaganda, and I mean that in a good way, like Ayn Rand&rsquo;s &ldquo;evil&rdquo; twin. The story itself is gripping, and it&rsquo;s based on real events.</p> <p>There&rsquo;s no denying that workers lived miserable lives in the early 20th century, and this book captures the spirit of that time perfectly. One of the miners&rsquo; core demands was an eight-hour workday, something that sounds perfectly reasonable today. But now imagine someone pushing for a four-hour workday. There&rsquo;s no perfect number of hours, and social norms are stubborn things to change.</p> <p>The author clearly pushes for labor unions and takes aim at capitalism, but the real problem in those company towns was monopoly. As usual, the issue is about control, the ability of one group to dominate another. At that scale, labor unions, governments, and big business are all capable of being just as cruel and dangerous. There are no good guys when power gets that concentrated.</p> First Year of BTC Map https://bubelov.com/blog/2023/btcmap/ Sat, 05 Aug 2023 00:00:00 +0000 https://bubelov.com/blog/2023/btcmap/ <p>It’s been a year since we launched the latest reincarnation of <a href="https://btcmap.org/">BTC Map</a>, and the results so far are encouraging. We started with a legacy dataset of more than 8,000 Bitcoin-accepting merchants, then had to verify every single one. Turns out, over 90% of them were either out of business or no longer taking bitcoin.</p> <p>Now we&rsquo;re at about 8,500 verified merchants, and we&rsquo;ve established a few solid ways to keep the data fresh. More importantly, we&rsquo;ve encouraged people to form local, location-based communities. I was skeptical at first, but well-run groups can really help local bitcoin users and travelers. They&rsquo;ll also become the rallying points when governments inevitably turn hostile toward Bitcoin.</p> Deleting My Patreon Account https://bubelov.com/blog/2023/patreon/ Fri, 04 Aug 2023 00:00:00 +0000 https://bubelov.com/blog/2023/patreon/ <p>Here is a quote from an e-mail I received from Patreon this morning:</p> <blockquote> <p>Hi there,</p> <p>Recently, one or more of your Patreon payments may have been declined and erroneously flagged as fraudulent by your bank. We’ve traced the issue back to system upgrades required by one of our payment processing partners. You may see some payments declined while the changes take effect:</p> <ol> <li> <p>If you are experiencing declined payments, please try the following steps: Contact your card issuer to confirm the charge is valid, and retry that card</p> </li> <li> <p>If retrying does not work, please try a different payment method. You can edit your payment method in your Settings, under Billing History.</p> </li> </ol> </blockquote> <p>While it&rsquo;s not entirely their fault, fully relying on third-party payment processors is a conscious choice. From now on, I&rsquo;ll be only supporting the projects which take direct donations in bitcoins, since I just don&rsquo;t have time to deal with all the friction created by the fiat system.</p> Growing Vegetables in Thailand https://bubelov.com/blog/2023/veggies/ Wed, 02 Aug 2023 00:00:00 +0000 https://bubelov.com/blog/2023/veggies/ <p><a href="https://bubelov.com/blog/2023/first-harvest/">Part 2</a></p> <p>I started with tomatoes and cucumbers, will see if they survive in tropics.</p> <p><figure> <a href="https://bubelov.com/blog/2023/veggies/cucumbers_hu_cb78dde4569b42f5.webp"> <img src="https://bubelov.com/blog/2023/veggies/cucumbers_hu_20821d8e476ae23.webp" alt="" /> </a> </figure></p> <p><figure> <a href="https://bubelov.com/blog/2023/veggies/tomatoes_hu_125224b586e45866.webp"> <img src="https://bubelov.com/blog/2023/veggies/tomatoes_hu_b2f47fe13de72262.webp" alt="" /> </a> </figure></p> AirGradient Pro Kit Review https://bubelov.com/blog/2023/airgradient-pro-kit-review/ Thu, 22 Jun 2023 00:00:00 +0000 https://bubelov.com/blog/2023/airgradient-pro-kit-review/ <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#the-problem">The Problem</a></li> <li><a href="#how-to-choose-an-air-quality-monitor">How to Choose an Air Quality Monitor</a></li> <li><a href="#delivery">Delivery</a></li> <li><a href="#onboarding">Onboarding</a></li> <li><a href="#assembly">Assembly</a></li> <li><a href="#accuracy">Accuracy</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="the-problem">The Problem</h2> <p>Clean air is becoming a scarce for many reasons beyond the scope of this article, so it&rsquo;s not surprising that the air purifier sales are on the rise. Although home ventilation and air purification are necessary, it can be easily overdone, which leads to higher electric bills and causes noise pollution (air treatment is a noisy process). The only way to know if your air is good enough is to have an air quality monitor, and there are plenty of good options on the market, although most of them have some <a href="https://www.airgradient.com/blog/hidden-costs-of-air-quality-monitoring/">serious downsides</a>.</p> <h2 id="how-to-choose-an-air-quality-monitor">How to Choose an Air Quality Monitor</h2> <p>Unfortunately, unchecked profit seeking can lead to some perverse incentives, and smart home device market is one of the shadiest ones. Most air quality monitors above $200 are accurate enough to tell you if you have a problem with your indoor air quality, but they can&rsquo;t control your air treatment systems, so they aren&rsquo;t able to fix any issues they detect.</p> <p>Most private companies hate open protocols and interoperability, because giving users more freedom and flexibility can hurt their profits, but there are a few companies, such as <a href="https://www.airgradient.com/">AirGradient</a>, which tend to restore my faith in humanity.</p> <p>AirGradient products seemed good and affordable enough (~$100 for a high accuracy monitor), so I decided to try their so-called Pro Kit, which is basically a reasonable accurate air quality monitor with an open source firmware and hardware.</p> <h2 id="delivery">Delivery</h2> <p>The delivery took seven days, and it wasn&rsquo;t easy to figure out how to track it. That&rsquo;s one of the things which can be improved and I let the company know about that issue. It&rsquo;s a pretty small company, so I expected some minor issues and inconveniences, because it&rsquo;s a small price to pay for a product with this level of transparency and repairability.</p> <p>As you can see, the box itself was manually prepared by someone, which gives the product an uncommon, hand-made feeling:</p> <p><figure> <a href="https://bubelov.com/blog/2023/airgradient-pro-kit-review/package_hu_3320b3346749a682.webp"> <img src="https://bubelov.com/blog/2023/airgradient-pro-kit-review/package_hu_67ac549786124f52.webp" alt="" /> </a> </figure></p> <p>The box itself had minor bumps here and there, but there was no damage to its contents:</p> <p><figure> <a href="https://bubelov.com/blog/2023/airgradient-pro-kit-review/box_hu_346ecf156d9b9930.webp"> <img src="https://bubelov.com/blog/2023/airgradient-pro-kit-review/box_hu_31bf3ded46244e63.webp" alt="" /> </a> </figure></p> <p><figure> <a href="https://bubelov.com/blog/2023/airgradient-pro-kit-review/sustainability_hu_5d29a689cc7fd007.webp"> <img src="https://bubelov.com/blog/2023/airgradient-pro-kit-review/sustainability_hu_2c7b5f9a48f7880e.webp" alt="" /> </a> </figure></p> <h2 id="onboarding">Onboarding</h2> <p>I expected to find an assembly instructions in the package, but I found a QR code instead. Scanning that code redirects you to AirGradient website, where you can find the assembly instructions. Websites are more ephemeral than the physical boxes, so I don&rsquo;t really like the idea of introducing such a dependency, but it makes total sense if you want to cut costs and be able to edit the assembly instructions after shipping the product.</p> <p>Anyway, I hope there will be a way of getting a printed manual in the future.</p> <p><figure> <a href="https://bubelov.com/blog/2023/airgradient-pro-kit-review/welcome_hu_c44b75a3f7666ecb.webp"> <img src="https://bubelov.com/blog/2023/airgradient-pro-kit-review/welcome_hu_9a2dfa1bdac0f9bc.webp" alt="" /> </a> </figure></p> <h2 id="assembly">Assembly</h2> <p>I ordered a pre-soldered kit, so the only thing I needed to do is to connect a few components to the mainboard and close the case. Some components, such as display and PM 2.5 sensor, were already connected, so I only had to connect a microcontroller, a CO2 sensor, and the unified temperature and humidity sensor:</p> <p><figure> <a href="https://bubelov.com/blog/2023/airgradient-pro-kit-review/board_hu_908bbced4cd1b27c.webp"> <img src="https://bubelov.com/blog/2023/airgradient-pro-kit-review/board_hu_daee3a2f11968057.webp" alt="" /> </a> </figure></p> <p><figure> <a href="https://bubelov.com/blog/2023/airgradient-pro-kit-review/sensors_hu_f597844c294ff7f5.webp"> <img src="https://bubelov.com/blog/2023/airgradient-pro-kit-review/sensors_hu_51e4fe540433bceb.webp" alt="" /> </a> </figure></p> <p><figure> <a href="https://bubelov.com/blog/2023/airgradient-pro-kit-review/assembled_hu_5511f2e6013b4aec.webp"> <img src="https://bubelov.com/blog/2023/airgradient-pro-kit-review/assembled_hu_6df56f025849487f.webp" alt="" /> </a> </figure></p> <h2 id="accuracy">Accuracy</h2> <p>AirGradient Pro only uses well-known and affordable sensors, which are accurate and reliable enough to be used to monitor air quality at home. Those sensors are far more accurate than the stuff you would find in an average sub $100 air quality monitor. Here is a picture of one of the most popular budget air quality monitors standing nearby AirGradient Pro:</p> <p><figure> <a href="https://bubelov.com/blog/2023/airgradient-pro-kit-review/diff_hu_ae7304c252bef997.webp"> <img src="https://bubelov.com/blog/2023/airgradient-pro-kit-review/diff_hu_3a355e542538a92b.webp" alt="" /> </a> </figure></p> <p>I just stepped into a well-ventilated room before taking that photo, and the other sensor immediately jumped from ~400 to ~1,300 CO2 ppm, which is simply impossible. AirGradient numbers felt a bit off, too, since I expected ~450 ppm, but it&rsquo;s known to show somewhat less accurate readings till it self-calibrates, which is happening automatically once a week. I checked the readings after the first self-calibration, and they were within the advertised +- 70 ppm precision.</p> <h2 id="conclusion">Conclusion</h2> <p>I&rsquo;m happy with my AirGradient Pro, and I&rsquo;ll probably order more monitors, because I want to monitor every room as well as the air quality outside my house. I enjoyed assembling this kit, and it also reinvigorated my interest in microcontrollers. It reminded me that I have an old Arduino kit, which I bought ten years ago, and I was finally motivated enough to check it out and finish all the projects described in its official guide.</p> AirGradient https://bubelov.com/blog/2023/airgradient/ Fri, 09 Jun 2023 00:00:00 +0000 https://bubelov.com/blog/2023/airgradient/ <p>I&rsquo;m renovating my house in Thailand and one of my goals is to make the house more airtight. Better heat insulation cuts my energy bills, and it also separates the internal and external air, which means that the external air pollution can&rsquo;t affect my house. Opening your windows to ventilate your house can sometimes do more harm than good, because it&rsquo;s wrong to assume that the outside air is always better than the air inside your house.</p> <p>The most popular air quality markers are <a href="https://en.wikipedia.org/wiki/Carbon_dioxide">CO2</a> and <a href="https://en.wikipedia.org/wiki/Particulates">PM2.5</a>, and the goal of a good ventilation system is keepling both of those measurements within a safe range.</p> <p>If you want to have a safe level of CO2, you need to ventilate your house somehow. We&rsquo;re all need to breathe and the act of breathing generates CO2 which needs to exit your house somehow. Disposing the old air isn&rsquo;t that trivial, and you also need to replace is with the new air to keep your air pressure in balance, and the new air coming from the street can have an unsafe level of PM2.5 and other pollutants.</p> <p>As you can see, CO2 and PM2.5 are slightly at odds, and excessive ventilation can also increase your energy bills, so it&rsquo;s impossible to design a good ventilation system without measuring the air quality somehow. If your CO2 level is healthy, there aren&rsquo;t any good reasons to ventilate your house, it&rsquo;s just a waste of energy and money.</p> <p>Measuring the air quality is tricky, since most air quality monitors are crap. I heard a lot of good things about <a href="https://aranet.com/products/aranet4/">Aranet4</a>, but it&rsquo;s pretty expensive, so I was looking for a cheaper alternative which can be as accurate as that product. A few days ago, I stumbled upon an <a href="https://www.airgradient.com/">AirGradient</a> website, and the products listed there look too good to be true. I decided to order a pre-soldered kit and test its performance, and I hope I won&rsquo;t be disappointed.</p> Philips Hue https://bubelov.com/blog/2023/hue/ Sun, 28 May 2023 00:00:00 +0000 https://bubelov.com/blog/2023/hue/ <p><a href="https://en.wikipedia.org/wiki/Matter_(standard)">Matter</a> is an open and privacy-friendly protocol which allows smart home devices to communicate with each other. I&rsquo;ve been reading a lot of good things about it, and I finally decided to buy a few of those Matter-enabled things, namely a <a href="https://en.wikipedia.org/wiki/Philips_Hue">Hue</a> Bridge and a bunch of Hue light bulbs.</p> <p>Those toys aren&rsquo;t cheap, so my expectations were high, and I must say, I&rsquo;m really impressed with the quality of those light bulbs. Everything works like a charm, and I really like the flexibility of that system. I set my outdoor lights to turn on automatically fifteen minutes before sunset and then turn off right after midnight.</p> <p>My bedroom doesn&rsquo;t really need any timers, but I often change the light temperature and intensity based on what I&rsquo;m doing. I like to start my evening with a bright cold light and then gradually move to a dimmed down warm light as I&rsquo;m preparing for sleep.</p> Nostr https://bubelov.com/blog/2023/nostr/ Sun, 14 May 2023 00:00:00 +0000 https://bubelov.com/blog/2023/nostr/ <p>I&rsquo;ve been playing with the <a href="https://nostr.com/">Nostr</a> protocol for a few hours, and here are my main takeaways:</p> <ul> <li>It has much better fundamentals than Mastodon, and the people behind it are nicer and more competent.</li> <li>It can scale, although it won&rsquo;t be trivial. I don&rsquo;t believe it would need to achieve massive scale in order to be useful, anyway.</li> <li>It&rsquo;s trivial to own and backup your data, you&rsquo;re in no way dependent on any server.</li> </ul> <p>Although the success of Nostr is not certain, I decided to create a simple Nostr to Atom (or RSS) bridge which will allow me to subscribe to Nostr accounts and see their posts in my <a href="https://github.com/bubelov/news">News app</a> feed.</p> Living Without an AC https://bubelov.com/blog/2023/ac/ Thu, 11 May 2023 00:00:00 +0000 https://bubelov.com/blog/2023/ac/ <p>I had to live without an AC for a few months, due to some renovation hiccups. Surprisingly, it&rsquo;s not a big deal even in Thailand. A simple fan and plenty of ventilation can make it comfortable to stay inside a house even when your thermometer shows +35°. A typical fan draws about 50 W at full speed, so you can run it 24/7 and burn just 36 kW⋅h a month, which is about $5, according to local tariff.</p> <p>I ordered the most power-efficient AC units on the market (SEER ~26, COP ~5), and they should be installed in a few days, but I doubt they will even come close to what a simple fan can provide in terms of price to value.</p> Dell Laptops Refuse to Install Security Updates https://bubelov.com/blog/2023/dell/ Wed, 10 May 2023 00:00:00 +0000 https://bubelov.com/blog/2023/dell/ <p>My laptop battery died a few weeks ago. I removed it and kept using my Dell XPS 13 with no issues. Everything worked like a charm, so I wasn&rsquo;t in a hurry to buy a new one.</p> <p>Today, I got notified of a new firmware security update and decided to install it right away, but, apparently, you can&rsquo;t update your Dell laptop&rsquo;s firmware without a battery attached.</p> <p>Blocking important security updates on perfectly capable hardware is just beyond stupid. But I guess it would be less of an issue if Dell batteries lasted more than a couple of years.</p> BTC Map https://bubelov.com/blog/2022/btcmap/ Sun, 06 Nov 2022 00:00:00 +0000 https://bubelov.com/blog/2022/btcmap/ <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#what-is-money">What is Money?</a></li> <li><a href="#paying-with-stocks">Paying With Stocks</a></li> <li><a href="#onchain-payments-are-slow-and-expensive">Onchain Payments are Slow and Expensive</a></li> <li><a href="#lightning-network">Lightning Network</a></li> <li><a href="#introducing-btc-map">Introducing BTC Map</a></li> </ul> </nav> </div> <h2 id="what-is-money">What is Money?</h2> <p>Even though Bitcoin has been around for almost fifteen years, people still struggle to describe what it actually is. Seven years ago, I wrote <a href="https://bubelov.com/blog/2015/mastering-bitcoin/">a post</a> where I listed a few basic features of money:</p> <ol> <li>It can act as a store of value.</li> <li>It can work as a medium of exchange.</li> <li>It can serve as a unit of account.</li> </ol> <p>Bitcoin has worked as a store of value since day one, but it&rsquo;s still missing the other two basic functions of money. To me, Bitcoin today feels more like a tech stock than a currency. It doesn&rsquo;t seem strange to us that nobody prices things in Google shares or accepts them as payment, does it?</p> <h2 id="paying-with-stocks">Paying With Stocks</h2> <p>Actually, I think a debit card linked to an index fund would be a great tool, stocks usually perform better than cash. But stocks lack the most essential property of a currency: the ability to spend them freely without asking for permission.</p> <p>Most merchants still accept cash, so if a cashless payment gets blocked for any reason, you can always fall back on physical money. All fiat currencies can be converted to cash and spent anonymously and permissionlessly, at least for now.</p> <p>With stocks or index funds, you can&rsquo;t do anything like that. You can&rsquo;t just transfer them to anyone, and you can&rsquo;t be sure your broker will approve the transfer. For that reason, stocks work only as a store of value. They&rsquo;re completely unusable as money.</p> <h2 id="onchain-payments-are-slow-and-expensive">Onchain Payments are Slow and Expensive</h2> <p>So what about bitcoin? It already works as a store of value, but like stocks, it&rsquo;s not used as a medium of exchange or a unit of account. The main reason is simple: on-chain transactions are too slow and too expensive for everyday spending.</p> <p>I once tried to buy a coffee with bitcoin and had to wait half an hour for a miner confirmation. Sure, some merchants might accept zero‑confirmation transactions, but that&rsquo;s pretty reckless, no wonder there aren&rsquo;t more places that accept Bitcoin today.</p> <h2 id="lightning-network">Lightning Network</h2> <p>Bitcoin has no physical form, and making instant, permissionless cash‑like payments is a very hard problem to solve. In fact, it took Bitcoin developers about fifteen years to build a usable system that lets merchants receive bitcoin instantly with very low fees.</p> <p>That system is the Lightning Network, and it&rsquo;s being actively deployed around the world right now.</p> <h2 id="introducing-btc-map">Introducing BTC Map</h2> <p><a href="https://btcmap.org/">BTC Map</a> is a project I&rsquo;ve been working on <a href="https://bubelov.com/blog/2022/btc-map/">since May</a>, and now we&rsquo;re a small team improving our software and data. Everything we build: data, apps, and infrastructure, is open source.</p> <p>Our goal is to make spending bitcoin as easy as spending cash. Turning Bitcoin into a real medium of exchange is still one of the biggest unsolved problems, and I hope we can make a dent in it.</p> Tailscale https://bubelov.com/blog/2022/tailscale/ Wed, 19 Oct 2022 00:00:00 +0000 https://bubelov.com/blog/2022/tailscale/ <p>I’ve been running WireGuard for years and it’s been solid. Lately, there’s a whole cottage industry of projects trying to slap a shiny GUI on WireGuard to make it &ldquo;easy&rdquo; for non-tech people. Tailscale is the one everyone won&rsquo;t shut up about, so I decided to check it out.</p> <p>Verdict: total letdown. It was more annoying to set up than plain WireGuard, and it forces you to use some third-party login. Maybe that makes sense in a corporate hellscape, but certainly not for my self-hosted stuff.</p> The Crowd https://bubelov.com/blog/2022/the-crowd/ Tue, 06 Sep 2022 00:00:00 +0000 https://bubelov.com/blog/2022/the-crowd/ <p>This book by Gustave Le Bon has some interesting takes on mass psychology, although I wouldn&rsquo;t call it scientific in any way. The author presents a pretty cynical worldview, claiming we all harbor dark instincts beneath our civility. It takes constant effort to suppress them, but they can break free quickly under the right conditions.</p> <p>Here are a few quotes I found interesting:</p> <blockquote> <p>The substitution of the unconscious action of crowds for the conscious activity of individuals is one of the principal characteristics of the present age.</p> </blockquote> <blockquote> <p>The reason is, that the most attentive observation of the facts of history has invariably demonstrated to me that social organisms being every whit as complicated as those of all beings, it is in no wise in our power to force them to undergo on a sudden far-reaching transformations. Nature has recourse at times to radical measures, but never after our fashion, which explains how it is that nothing is more fatal to a people than the mania for great reforms, however excellent these reforms may appear theoretically.</p> </blockquote> <blockquote> <p>The complexity of social facts is such, that it is impossible to grasp them as a whole and to foresee the effects of their reciprocal influence. It seems, too, that behind the visible facts are hidden at times thousands of invisible causes.</p> </blockquote> <blockquote> <p>The part played by the unconscious in all our acts is immense, and that played by reason very small. The unconscious acts like a force still unknown.</p> </blockquote> <blockquote> <p>The ideas of the past, although half destroyed, being still very powerful, and the ideas which are to replace them being still in process of formation, the modern age represents a period of transition and anarchy.</p> </blockquote> <blockquote> <p>Science promised us truth, or at least a knowledge of such relations as our intelligence can seize: it never promised us peace or happiness. Sovereignly indifferent to our feelings, it is deaf to our lamentations. It is for us to endeavour to live with science, since nothing can bring back the illusions it has destroyed.</p> </blockquote> <blockquote> <p>For instance, should a legislator, wishing to impose a new tax, choose that which would be theoretically the most just? By no means. In practice the most unjust may be the best for the masses. Should it at the same time be the least obvious, and apparently the least burdensome, it will be the most easily tolerated. It is for this reason that an indirect tax, however exorbitant it be, will always be accepted by the crowd, because, being paid daily in fractions of a farthing on objects of consumption, it will not interfere with the habits of the crowd, and will pass unperceived.</p> </blockquote> <blockquote> <p>The momentary revolutionary instincts of crowds do not prevent them from being extremely conservative⁠—Crowds instinctively hostile to changes and progress.</p> </blockquote> <blockquote> <p>Their sympathies have never been bestowed on easygoing masters, but on tyrants who vigorously oppressed them. It is to these latter that they always erect the loftiest statues. It is true that they willingly trample on the despot whom they have stripped of his power, but it is because, having lost his strength, he has resumed his place among the feeble, who are to be despised because they are not to be feared.</p> </blockquote> <blockquote> <p>Had democracies possessed the power they wield today at the time of the invention of mechanical looms or of the introduction of steam-power and of railways, the realisation of these inventions would have been impossible, or would have been achieved at the cost of revolutions and repeated massacres. It is fortunate for the progress of civilisation that the power of crowds only began to exist when the great discoveries of science and industry had already been effected.</p> </blockquote> <blockquote> <p>Appeals to sentiments of glory, honour, and patriotism are particularly likely to influence the individual forming part of a crowd, and often to the extent of obtaining from him the sacrifice of his life.</p> </blockquote> <blockquote> <p>Intolerance and fanaticism are the necessary accompaniments of the religious sentiment. They are inevitably displayed by those who believe themselves in the possession of the secret of earthly or eternal happiness. These two characteristics are to be found in all men grouped together when they are inspired by a conviction of any kind.</p> </blockquote> <blockquote> <p>Philosophers and historians have endeavoured in vain to prove its absurdity, but yet they have had no difficulty in demonstrating that institutions are the outcome of ideas, sentiments, and customs, and that ideas, sentiments, and customs are not to be recast by recasting legislative codes.</p> </blockquote> <blockquote> <p>Among the Anglo-Saxons and notably in America this same word “democracy” signifies, on the contrary, the intense development of the will of the individual, and as complete a subordination as possible of the State, which, with the exception of the police, the army, and diplomatic relations, is not allowed the direction of anything, not even of public instruction.</p> </blockquote> <blockquote> <p>Prestige in reality is a sort of domination exercised on our mind by an individual, a work, or an idea. This domination entirely paralyses our critical faculty, and fills our soul with astonishment and respect. The sentiment provoked is inexplicable, like all sentiments, but it would appear to be of the same kind as the fascination to which a magnetised person is subjected. Prestige is the mainspring of all authority. Neither gods, kings, nor women have ever reigned without it.</p> </blockquote> <blockquote> <p>The philosophic absurdity that often marks general beliefs has never been an obstacle to their triumph. Indeed the triumph of such beliefs would seem impossible unless on the condition that they offer some mysterious absurdity. In consequence, the evident weakness of the socialist beliefs of today will not prevent them triumphing among the masses.</p> </blockquote> <blockquote> <p>Moreover, the parliamentary system represents the ideal of all modern civilised peoples. The system is the expression of the idea, psychologically erroneous, but generally admitted, that a large gathering of men is much more capable than a small number of coming to a wise and independent decision on a given subject.</p> </blockquote> <blockquote> <p>On occasion, the leader may be intelligent and highly educated, but the possession of these qualities does him, as a rule, more harm than good. By showing how complex things are, by allowing of explanation and promoting comprehension, intelligence always renders its owner indulgent, and blunts, in a large measure, that intensity and violence of conviction needful for apostles. The great leaders of crowds of all ages, and those of the Revolution in particular, have been of lamentably narrow intellect; while it is precisely those whose intelligence has been the most restricted who have exercised the greatest influence.</p> </blockquote> <blockquote> <p>Having reached a certain level of strength and complexity a civilisation ceases to grow, and having ceased to grow it is condemned to a speedy decline. The hour of its old age has struck.</p> </blockquote> Arch Linux on Raspberry Pi 4 https://bubelov.com/blog/2022/arch-linux-on-raspberry-pi-4/ Mon, 29 Aug 2022 00:00:00 +0000 https://bubelov.com/blog/2022/arch-linux-on-raspberry-pi-4/ <p>The most popular Linux distros for Raspberry Pi 4 are the official <a href="https://www.raspberrypi.com/software/">Raspberry Pi OS</a> and a <a href="https://ubuntu.com/download/raspberry-pi">special version</a> of Ubuntu. I&rsquo;d expect them to be pretty similar since both of them are Debian-based, but let&rsquo;s collect some high-level data to see if they have any fundamental differences:</p> <p>Raspberry Pi OS:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">raspinfo </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl">Raspberry Pi 4 Model B Rev 1.2 </span></span><span class="line"><span class="cl">PRETTY_NAME=&#34;Debian GNU/Linux 11 (bullseye)&#34; </span></span><span class="line"><span class="cl">NAME=&#34;Debian GNU/Linux&#34; </span></span><span class="line"><span class="cl">VERSION_ID=&#34;11&#34; </span></span><span class="line"><span class="cl">VERSION=&#34;11 (bullseye)&#34; </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl">Linux xxx 5.15.32-v8+ #1538 SMP PREEMPT Thu Mar 31 19:40:39 BST 2022 aarch64 GNU/Linux </span></span></code></pre></div><p>Ubuntu 20.04:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">lsb_release -a </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl">No LSB modules are available. </span></span><span class="line"><span class="cl">Distributor ID: Ubuntu </span></span><span class="line"><span class="cl">Description: Ubuntu 22.04.1 LTS </span></span><span class="line"><span class="cl">Release: 22.04 </span></span><span class="line"><span class="cl">Codename: jammy </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl">uname -a </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl">Linux xxx 5.15.0-1013-raspi #15-Ubuntu SMP PREEMPT Mon Aug 8 06:33:06 UTC 2022 aarch64 aarch64 aarch64 GNU/Linux </span></span></code></pre></div><p>Clearly, those are very similar distributions and they both run the same minor version of the same <a href="https://github.com/raspberrypi/linux">custom</a> Linux kernel. That&rsquo;s a bummer, I&rsquo;d like to see more diversity, and I prefer using mainline kernel, if possible, so I started looking for an alternative distribution which isn&rsquo;t Debian-based and which runs on a mainline Linux kernel out of the box. After reading about a few options, I finally settled on <a href="https://archlinuxarm.org/">Arch Linux ARM</a>.</p> <p>The first thing which I noticed is how fast it boots up, compared with Raspberry Pi OS and Ubuntu.</p> <p>Ubuntu 20.04:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">systemd-analyze </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl">Startup finished in 6.951s (kernel) + 13.325s (userspace) = 20.276s </span></span><span class="line"><span class="cl">graphical.target reached after 11.717s in userspace </span></span></code></pre></div><p>Arch Linux Arm:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">systemd-analyze </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl">Startup finished in 4.887s (kernel) + 7.442s (userspace) = 12.330s </span></span><span class="line"><span class="cl">graphical.target reached after 7.442s in userspace. </span></span></code></pre></div><p>That&rsquo;s a 60% reduction in startup time, which is pretty impressive. I&rsquo;ve been running Arch on my Raspberry Pi 4 for a few months already and the only issue I noticed was related to USB 3 port not mounting an external SSD automatically during the system boot. I had to move that drive to a USB 2 port since I didn&rsquo;t really need USB 3 bandwidth. This morning, I decided to check that USB 3 port again with the most recent mainline kernel, and it looks like this issue is gone in 5.19:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">uname -a </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl">Linux xxx 5.19.4-1-aarch64-ARCH #1 SMP PREEMPT Sat Aug 27 14:19:21 MDT 2022 aarch64 GNU/Linux </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl">lsblk | grep sda </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl">sda 8:0 0 931.5G 0 disk </span></span><span class="line"><span class="cl">`-sda1 8:1 0 931.5G 0 part /run/data </span></span></code></pre></div> News App Progress https://bubelov.com/blog/2022/news-app-progress-08/ Mon, 01 Aug 2022 00:00:00 +0000 https://bubelov.com/blog/2022/news-app-progress-08/ <p>I just finished cleaning up the codebase, and I&rsquo;m currently busy with a few performance optimizations for the most frequent tasks, namely:</p> <ul> <li>Fetching preview images for feed entries</li> <li>Importing feeds from OPML files</li> <li>Fetching new and updated feed entries in standalone mode</li> </ul> <p>The initial prototype processed HTTP requests one by one in a single thread, but I quickly switched to batch processing in order to speed things up. Each batch spawned a separate thread for every request, enabling the app to process a few requests in parallel. That helped, but the app still had to iterate through those batches sequentially, so every batch was always blocked by a previous batch, which in itself was blocked by its slowest request. That might be less of an issue if all your requests take the same time to execute, but it&rsquo;s certainly not the case for a feed reader app.</p> <p>It&rsquo;s possible to avoid batch processing and just spawn an independent thread for every request, but that approach won&rsquo;t scale. Spawning thousands of threads is wasteful, and it can lead to unpleasant side effects. Most smartphones can handle insane amounts of concurrent threads but those threads still need to compete for a single network connection, which might be pretty slow. In other words, having too much concurrent requests can add per-request latency.</p> <p>A better approach is to create a fixed number of concurrent and fully independent workers. A few slow queries can block some of those workers, but they&rsquo;re unlikely to bring the whole pool to a halt, which boosts total bandwidth without sacrificing per-request latency. There are many ways to implement such a feature, but I used this task as an excuse to try <a href="https://kotlinlang.org/docs/channels.html">Kotlin Channels</a>. Here is the quote from the doc:</p> <blockquote> <p>Multiple coroutines may receive from the same channel, distributing work between themselves</p> </blockquote> <p>This approach is called <a href="https://en.wikipedia.org/wiki/Fan-out">fan-out</a> and this term seems to be borrowed from digital electronics. I tried it, and it helped me to speed things up by an order of magnitude.</p> Beej's Guide to Network Programming https://bubelov.com/blog/2022/beej/ Tue, 26 Jul 2022 00:00:00 +0000 https://bubelov.com/blog/2022/beej/ <p>Although I don&rsquo;t use C professionally, I enjoy reading low-level documentation because it sheds more light on how things work under the hood. <a href="https://beej.us/guide/bgnet/">Beej&rsquo;s Guide to Network Programming</a> is a great book which is focused on network sockets and the ways we can interact with them.</p> <p>Things like <code>select</code> and <code>epoll</code> are the secret sauce which enables higher-level non-blocking IO abstractions such as Kotlin coroutines and async Rust, so this book can be of interest to anyone who&rsquo;s curious to see how the sausage gets made.</p> LineageOS https://bubelov.com/blog/2022/lineage-os/ Sat, 23 Jul 2022 00:00:00 +0000 https://bubelov.com/blog/2022/lineage-os/ <p>I have a few Android phones, but my favorite is the <a href="https://en.wikipedia.org/wiki/Xiaomi_Mi_A1">Xiaomi Mi A1</a>. It&rsquo;s a five‑year‑old phone, but it runs the latest Android thanks to <a href="https://lineageos.org/">LineageOS</a>. This phone stopped getting official updates years ago, and without LineageOS it wouldn&rsquo;t be usable, even though the hardware is perfectly capable. It&rsquo;s still blazing fast, which is a good reminder that two‑year (or even five‑year) software‑support cycles are indefensible. We should be able to use our hardware as long as it works, which can easily be close to ten years or more.</p> <p>I&rsquo;m not a fan of heavy‑handed regulation, so I get why companies don&rsquo;t want to support devices for more than a few years. Forcing them to offer software support for 10+ years is problematic for many reasons, one being that companies come and go, which makes such rules unenforceable.</p> <p>On the other hand, it&rsquo;s not hard to let customers install non‑official firmware. We could even let companies off the hook if they simply release open‑source drivers for their hardware, enabling others to keep offering software support long after the manufacturer has moved on.</p> News App Progress https://bubelov.com/blog/2022/news-app-progress-06/ Thu, 30 Jun 2022 00:00:00 +0000 https://bubelov.com/blog/2022/news-app-progress-06/ <p>Currently, many screen transitions are a bit messy, so I spent some time making sure the most important transitions are smooth. Making the app more resilient in experimental standalone mode was also high in my list of priorities. Lastly, I found some time to improve the test coverage, although it&rsquo;s more of a long-term effort, and it&rsquo;s not directly related to the next release.</p> Thunder https://bubelov.com/blog/2022/thunder/ Fri, 24 Jun 2022 00:00:00 +0000 https://bubelov.com/blog/2022/thunder/ <p><a href="https://github.com/lightning/bolts">Lightning Network</a> is a protocol which allows us to scale Bitcoin without compromising on it&rsquo;s most cherished properties such as decentralization and transaction finality. Currently, there are a few companies who develop their own implementations of the Lightning Network. Luckily for us, all of those implementations are interoperable since they follow the same protocol, so it&rsquo;s hard to make a wrong choice here.</p> <p>Personally, I&rsquo;ve been running <a href="https://github.com/LightningNetwork/lnd">LND</a> for quite some time, but I eventually settled on <a href="https://github.com/ElementsProject/lightning">Core Lightning</a>. Unfortunately, there are no native Android apps which can control a Core Lightning node, and making payments on the go is pretty essential for Lightning Network to take off.</p> <p>Core Lightning didn&rsquo;t even have a remote control API till <a href="https://medium.com/blockstream/core-lightning-v0-11-0-channel-multiplexing-a-new-api-and-much-more-5d34df8cfeb5">0.11.0</a> so it&rsquo;s still a bleeding edge stuff and there are a few rough corners we should be aware of. That said, the <a href="https://github.com/ElementsProject/lightning/tree/master/cln-grpc">new gRPC plugin</a> is pretty great, and it also brings up mTLS out of the box, so you don&rsquo;t have to worry about unauthorized clients messing with your node.</p> <p>I was pretty excited about this new gRPC API and I decided to create my own Android client. Most electricity-themed names were already taken, but Thunder seemed to be vacant, so <a href="https://github.com/bubelov/thunder">I took it</a>. This app can connect to your Core Lightning node via Tor, and it lets you see you balance as well as the list of you channels, and you can even make Lightning payments on the go. Some features are still missing though, but I&rsquo;m planning to improve this app in the coming months.</p> Canada https://bubelov.com/blog/2022/canada/ Sun, 19 Jun 2022 00:00:00 +0000 https://bubelov.com/blog/2022/canada/ <p>I&rsquo;ve finally joined the Express Entry pool, which is just a first tiny step towards a Canadian permanent residence. I had to verify my academic degree, and I also needed to fly to Bangkok to attend an IELTS exam which is supposed to verify my English language ability.</p> <p>The exam was easy and my final CRS score isn&rsquo;t that bad, although I&rsquo;ll probably re-take IELTS a few times until I have a better score. Bumping listening to 8.0 is realistic, and it would give me 50+ extra points, which is a huge competitive advantage.</p> <p>The best course of action now is squeezing as much extra points as possible and looking for a Canadian job. It&rsquo;s quite hard for a non-resident to find a local job, but it&rsquo;s definitely worth a try.</p> Bitcoin Release Unpacked - v27 https://bubelov.com/blog/2022/bitcoin-release/ Fri, 17 Jun 2022 00:00:00 +0000 https://bubelov.com/blog/2022/bitcoin-release/ <p>Every Bitcoin release is distributed as a single archive containing a lot of cryptic files. In fact, you rarely need all of them, but it&rsquo;s pretty important to understand the purpose of each and every file in this archive in order to pick the ones you might actually need.</p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#where-to-find-the-latest-release">Where to Find the Latest Release</a></li> <li><a href="#building-from-source-vs-getting-binaries">Building From Source vs Getting Binaries</a></li> <li><a href="#extracting-files-from-the-archive">Extracting Files From the Archive</a></li> <li><a href="#bin-directory">/bin Directory</a></li> <li><a href="#bitcoinconf">/bitcoin.conf</a></li> <li><a href="#include-directory">/include Directory</a></li> <li><a href="#lib-directory">/lib Directory</a></li> <li><a href="#share-directory">/share Directory</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="where-to-find-the-latest-release">Where to Find the Latest Release</h2> <p>Bitcoin&rsquo;s source code is currently <a href="https://github.com/bitcoin/bitcoin">hosted on GitHub</a>. When Bitcoin devs feel that their code is stable enough to be deployed in production, they tag it as a release and increment its version number. GitHub has a <a href="https://github.com/bitcoin/bitcoin/releases">nice interface</a> which allows us to see the latest release tags.</p> <p>The most recent release is tagged as <a href="https://github.com/bitcoin/bitcoin/releases/tag/v27.0">v27.0</a>, so that&rsquo;s the latest Bitcoin version, and it&rsquo;s the most sensible thing for us to download.</p> <h2 id="building-from-source-vs-getting-binaries">Building From Source vs Getting Binaries</h2> <p>Since Bitcoin is an open source project, you can fetch its source code and compile it yourself. However, this practice is discouraged because it&rsquo;s far more complicated compared with getting a pre-compiled package. Some might think that building from source is safer, but it couldn&rsquo;t be further from the truth. The archive with pre-built binaries is no less secure than the source code itself because it&rsquo;s protected by cryptographic signatures which you can verify if you suspect that your archive might have been tampered with.</p> <h2 id="extracting-files-from-the-archive">Extracting Files From the Archive</h2> <p>Every Bitcoin release has a link to the <a href="https://bitcoincore.org/bin/bitcoin-core-27.0/">page</a> where you can download an archive with pre-built binaries. You&rsquo;ll see a lot of archives there, but you only need the one which is built for your operating system and instruction set architecture. I&rsquo;m running Linux and I have an AMD CPU, so I need an <em>x86_64-linux-gnu</em> version:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">curl --output bitcoin-27.0.tar.gz https://bitcoincore.org/bin/bitcoin-core-27.0/bitcoin-27.0-x86_64-linux-gnu.tar.gz </span></span></code></pre></div><p>The name of the archive we just downloaded ends with <em>.tar.gz</em> which is supposed to give as a hint on how to deal with this file. It&rsquo;s common to assume that archives are always compressed, but you can create an uncompressed archive with a tool like <em>tar</em>. Since the compression is optional, it&rsquo;s a good practice to append the information about the compression method to the name of your archive. Bitcoin releases are always compressed with <em>gzip</em>, so that&rsquo;s the reason behind this weird file extension.</p> <p>Okay, now it&rsquo;s time to extract all the files from the archive we just downloaded:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">tar --extract --file bitcoin-27.0.tar.gz --verbose </span></span></code></pre></div><p>You should see the following output:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">bitcoin-27.0/ </span></span><span class="line"><span class="cl">bitcoin-27.0/README.md </span></span><span class="line"><span class="cl">bitcoin-27.0/bin/ </span></span><span class="line"><span class="cl">bitcoin-27.0/bin/bitcoin-cli </span></span><span class="line"><span class="cl">bitcoin-27.0/bin/bitcoin-qt </span></span><span class="line"><span class="cl">bitcoin-27.0/bin/bitcoin-tx </span></span><span class="line"><span class="cl">bitcoin-27.0/bin/bitcoin-util </span></span><span class="line"><span class="cl">bitcoin-27.0/bin/bitcoin-wallet </span></span><span class="line"><span class="cl">bitcoin-27.0/bin/bitcoind </span></span><span class="line"><span class="cl">bitcoin-27.0/bin/test_bitcoin </span></span><span class="line"><span class="cl">bitcoin-27.0/bitcoin.conf </span></span><span class="line"><span class="cl">bitcoin-27.0/include/ </span></span><span class="line"><span class="cl">bitcoin-27.0/include/bitcoinconsensus.h </span></span><span class="line"><span class="cl">bitcoin-27.0/lib/ </span></span><span class="line"><span class="cl">bitcoin-27.0/lib/libbitcoinconsensus.so </span></span><span class="line"><span class="cl">bitcoin-27.0/lib/libbitcoinconsensus.so.0 </span></span><span class="line"><span class="cl">bitcoin-27.0/lib/libbitcoinconsensus.so.0.0.0 </span></span><span class="line"><span class="cl">bitcoin-27.0/share/ </span></span><span class="line"><span class="cl">bitcoin-27.0/share/man/ </span></span><span class="line"><span class="cl">bitcoin-27.0/share/man/man1/ </span></span><span class="line"><span class="cl">bitcoin-27.0/share/man/man1/bitcoin-cli.1 </span></span><span class="line"><span class="cl">bitcoin-27.0/share/man/man1/bitcoin-qt.1 </span></span><span class="line"><span class="cl">bitcoin-27.0/share/man/man1/bitcoin-tx.1 </span></span><span class="line"><span class="cl">bitcoin-27.0/share/man/man1/bitcoin-util.1 </span></span><span class="line"><span class="cl">bitcoin-27.0/share/man/man1/bitcoin-wallet.1 </span></span><span class="line"><span class="cl">bitcoin-27.0/share/man/man1/bitcoind.1 </span></span><span class="line"><span class="cl">bitcoin-27.0/share/rpcauth/ </span></span><span class="line"><span class="cl">bitcoin-27.0/share/rpcauth/README.md </span></span><span class="line"><span class="cl">bitcoin-27.0/share/rpcauth/rpcauth.py </span></span></code></pre></div><p>Wow, that&rsquo;s a lot of files and dirs. Let&rsquo;s go through them one by one:</p> <h2 id="bin-directory">/bin Directory</h2> <p>First, let&rsquo;s check that the <em>/bin</em> directory also exists on our target machine. That&rsquo;s the path where we&rsquo;re expected to place our Bitcoin executables.</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">ls -l /bin </span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">lrwxrwxrwx 1 root root 7 Oct 18 21:01 /bin -&gt; usr/bin </span></span></code></pre></div><p>In many modern Linux distributions, both <em>/bin</em> and <em>/usr/bin</em> point to the same directory, so <em>/bin</em> is just an alias for <em>/usr/bin</em>. This sounds like the best place to keep your Bitcoin binaries. If you have any doubts, you can always check <a href="https://refspecs.linuxfoundation.org/FHS_3.0/fhs/ch04s04.html">Filesystem Hierarchy Standard</a>.</p> <p>But wait, what about <a href="https://refspecs.linuxfoundation.org/FHS_3.0/fhs/ch04s09.html">this entry</a>?</p> <blockquote> <p>Locally installed software must be placed within <em>/usr/local</em> rather than <em>/usr</em> unless it is being installed to replace or upgrade software in <em>/usr</em>.</p> </blockquote> <p>It turns out, the best place to install Bitcoin binaries is <em>/usr/local/bin</em>. Now that we know where to install those binaries, let&rsquo;s check what they actually do:</p> <table> <thead> <tr> <th>File</th> <th>Purpose</th> </tr> </thead> <tbody> <tr> <td>bitcoin-cli</td> <td>Command line tool which can talk to a running <em>bitcoind</em> or <em>bitcoin-qt</em> instance.</td> </tr> <tr> <td>bitcoind</td> <td>Bitcoin Core server, without GUI included.</td> </tr> <tr> <td>bitcoin-qt</td> <td>Qt-based GUI with a built-in Bitcoin Core server.</td> </tr> <tr> <td>bitcoin-tx</td> <td>A standalone tool which can help you manage raw transactions.</td> </tr> <tr> <td>bitcoin-util</td> <td>It can provide functionality that does not rely on the ability to access a running Bitcoin Server. The only available command so far is <em>grind</em> which takes a hex-encoded header and grinds its nonce until its <em>nBits</em> is satisfied.</td> </tr> <tr> <td>bitcoin-wallet</td> <td>A standalone tool which is used to manage Bitcoin Core wallet.dat files. Note that Bitcoin Core server (bitcoin-qt or bitcoind) can manage wallets by itself so this tool is completely optional.</td> </tr> <tr> <td>test_bitcoin</td> <td>The binary that implements all of Bitcoin Core&rsquo;s unit tests. It has been verified to pass all tests when this build of Bitcoin Core was created, but you can always repeat those tests if you feel like it.</td> </tr> </tbody> </table> <h2 id="bitcoinconf">/bitcoin.conf</h2> <p>Most programs can be configured to better fit a particular environment and Bitcoin Core is no exception. Older versions of Bitcoin Core didn&rsquo;t really have an easy way to see all the possible configuration parameters and their purpose, and that led to the introduction of the &ldquo;skeleton&rdquo; conf file. This file can be copied to your data directory, but it doesn&rsquo;t change any configuration options, by default. You&rsquo;re supposed to open this file, find the option you want to adjust, and uncomment the string which enables this option. This way, you can clearly see the default values for every possible configuration option, as well as any active overrides.</p> <h2 id="include-directory">/include Directory</h2> <p>As usual, it&rsquo;s always a good idea to check the latest <a href="https://refspecs.linuxfoundation.org/FHS_3.0/fhs/index.html">Filesystem Hierarchy Standard</a>.</p> <blockquote> <p>/usr/include - This is where all the system&rsquo;s general-use include files for the C programming language should be placed.</p> </blockquote> <p>Again, it&rsquo;s safer to use <em>/usr/local/include</em> instead of <em>/usr/include</em> in order to avoid conflicts with your package manager. We have only a single file to include and here is what it does:</p> <table> <thead> <tr> <th>File</th> <th>Purpose</th> </tr> </thead> <tbody> <tr> <td>bitcoinconsensus.h</td> <td>C header file for those who want to build software using the <em>libbitcoinconsensus.so</em> library.</td> </tr> </tbody> </table> <h2 id="lib-directory">/lib Directory</h2> <blockquote> <p>/usr/lib includes object files and libraries. On some systems, it may also include internal binaries that are not intended to be executed directly by users or shell scripts.</p> </blockquote> <p>It looks like <em>/lib</em> follows the same pattern as <em>/bin</em>, and <em>/lib</em> is just an alias for <em>/usr/lib</em>. As usual, it&rsquo;s better to use <em>/usr/local/lib</em> in order to avoid collisions.</p> <table> <thead> <tr> <th>File</th> <th>Purpose</th> </tr> </thead> <tbody> <tr> <td>libbitcoinconsensus.so</td> <td>Symlink to libbitcoinconsensus.so.0.0.0</td> </tr> <tr> <td>libbitcoinconsensus.so.0</td> <td>Symlink to libbitcoinconsensus.so.0.0.0</td> </tr> <tr> <td>libbitcoinconsensus.so.0.0.0</td> <td>Shared library that you can link your own software against. It implements the exact same script validation rules as Bitcoin Core does, thus avoiding the need to reimplement consensus logic yourself.</td> </tr> </tbody> </table> <p>So, this directory contains a single shared library and two symlinks which simply point to its location. This is a standard versioning convention, so it&rsquo;s better to copy all of those files if you&rsquo;re planning to use <em>libbitcoinconsensus</em>.</p> <h2 id="share-directory">/share Directory</h2> <p>The last directory included in our release is <em>/share</em>, so let&rsquo;s first figure out its purpose.</p> <blockquote> <p>Any program or package which contains or requires data that doesn&rsquo;t need to be modified should store that data in /usr/share (or /usr/local/share, if installed locally)</p> </blockquote> <p>Well, man pages aren&rsquo;t supposed to be edited, so it would make perfect sense to place them in <em>/usr/local/share</em> directory.</p> <table> <thead> <tr> <th>File</th> <th>Purpose</th> </tr> </thead> <tbody> <tr> <td>man/man1/bitcoin-cli.1</td> <td>man page for bitcoin-cli binary</td> </tr> <tr> <td>man/man1/bitcoind.1</td> <td>man page for bitcoind binary</td> </tr> <tr> <td>man/man1/bitcoin-qt.1</td> <td>man page for bitcoin-qt binary</td> </tr> <tr> <td>man/man1/bitcoin-tx.1</td> <td>man page for bitcoin-tx binary</td> </tr> <tr> <td>man/man1/bitcoin-util.1</td> <td>man page for bitcoin-util binary</td> </tr> <tr> <td>man/man1/bitcoin-wallet.1</td> <td>man page for bitcoin-wallet binary</td> </tr> </tbody> </table> <p>It&rsquo;s generally a good idea to install manuals for all the tools you&rsquo;re planning to use because they tend to be quite informative, and they don&rsquo;t need an Internet connection to be accessed.</p> <p>There is also a folder called <em>rpcauth</em>, and it contains a simple script used to generate JSON-RPC users.</p> <table> <thead> <tr> <th>File</th> <th>Purpose</th> </tr> </thead> <tbody> <tr> <td>rpcauth/README.md</td> <td>Explains how to use rpcauth.py</td> </tr> <tr> <td>rpcauth/rpcauth.py</td> <td>A script used to create JSON-RPC users</td> </tr> </tbody> </table> <h2 id="conclusion">Conclusion</h2> <p>Bitcoin releases tend to contain a lot of files, so it might be hard to understand what&rsquo;s going on and which files are necessary for your particular use. For a casual desktop user, I&rsquo;d recommend installing only the following files:</p> <ul> <li>/bin/bitcoin-cli</li> <li>/bin/bitcoin-qt</li> <li>/share/man/man1/bitcoin-cli.1</li> <li>/share/man/man1/bitcoin-qt.1</li> </ul> <p>If you don&rsquo;t have or don&rsquo;t need a GUI and you want to run a headless node, this set of files might better fit your particular case:</p> <ul> <li>/bin/bitcoin-cli</li> <li>/bin/bitcoind</li> <li>/share/man/man1/bitcoin-cli.1</li> <li>/share/man/man1/bitcoind.1</li> </ul> Bitcoin Contributions https://bubelov.com/blog/2022/bitcoin-contributions/ Tue, 14 Jun 2022 00:00:00 +0000 https://bubelov.com/blog/2022/bitcoin-contributions/ <p>I&rsquo;ve been digging into Bitcoin in my spare time, and it improved my understanding of the whole Bitcoin development process. Submitting changes and proposing new ideas ended up as hard as I expected it to be, but it&rsquo;s possible to change stuff in reasonable time, if you can explain why it&rsquo;s necessary.</p> <p>The Bitcoin itself is a well-organized project maintained by a sizable group of experienced devs, but you shouldn&rsquo;t have the same expectations about other core infrastructure projects in this space. Some of those projects are wild and chaotic, but it&rsquo;s generally possible to make meaningful changes as an outsider, although you might need to be more pushy.</p> Energy Bill - Part 2 https://bubelov.com/blog/2022/energy-bill-2/ Fri, 03 Jun 2022 00:00:00 +0000 https://bubelov.com/blog/2022/energy-bill-2/ <p>In my <a href="https://bubelov.com/blog/2022/energy-bill-1/">previous post</a> I mentioned that I was planning to cut my energy bill, and my power consumption target was rather ambitious. I aimed at 5-fold decrease, but it assumed never turning on air-cons, which made me feel uncomfortable, especially at night. That said, I was still able to cut the bill in half without any damage to my quality of life.</p> <p>The most savings came from not keeping the lights on when it&rsquo;s not necessary and from using a fan and an open window instead of an air-con in my office. It&rsquo;s pretty clear that rising energy prices can actually be beneficial because my current bill is even smaller than it was a year ago. Price is a nice signal, and it currently nags us to stop wasting energy.</p> News App Progress https://bubelov.com/blog/2022/news-app-progress-05/ Tue, 31 May 2022 00:00:00 +0000 https://bubelov.com/blog/2022/news-app-progress-05/ <p>It&rsquo;s been more than three months since I released <a href="https://github.com/bubelov/news">News</a> v0.3.4, so I should probably publish a new release in a few weeks. The next version will finally have smoothed out screen transitions, and I&rsquo;m also planning to promote built-in standalone backend out of beta. I had some progress with minimizing external dependencies and making standalone parser more standards-compliant, but this is a long-running effort, and it&rsquo;ll take many more releases to reach the desired results.</p> BTC Map https://bubelov.com/blog/2022/btc-map/ Fri, 27 May 2022 00:00:00 +0000 https://bubelov.com/blog/2022/btc-map/ <p>This is my second open source Android app published on F-Droid. I wouldn&rsquo;t say that the publishing process was smooth since some F-Droid maintainers are biased against Bitcoin, but the app is now published and everything seems to be fine. It feels good to be able to avoid Google Play and still reach a pretty large audience.</p> <p>My initial plan was to use <a href="https://wiki.openstreetmap.org/wiki/Overpass_API">Overpass API</a> as a backend, but it looks like Overpass usually needs a few minutes to return all bitcoin-accepting places.</p> <p>There are two most obvious ways to solve this issue:</p> <ol> <li>Bundle the data with the app</li> <li>Set up a fast cache and place it between Android client and Overpass API</li> </ol> <p>I ended up combining both of those options and adding some GitHub magic on top of that. Here is how it works:</p> <ol> <li>I&rsquo;ve set up a public GitHub repository at: <a href="https://github.com/bubelov/btcmap-data">https://github.com/bubelov/btcmap-data</a></li> <li>This repository has <code>query.txt</code> and <code>data.json</code> in its root directory</li> <li>The sole purpose of <code>query.txt</code> is to explain what we need from Overpass API, namely:</li> </ol> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">[out:json][timeout:300]; </span></span><span class="line"><span class="cl">( </span></span><span class="line"><span class="cl"> node[&#34;currency:XBT&#34;=&#34;yes&#34;]; </span></span><span class="line"><span class="cl"> way[&#34;currency:XBT&#34;=&#34;yes&#34;]; </span></span><span class="line"><span class="cl"> relation[&#34;currency:XBT&#34;=&#34;yes&#34;]; </span></span><span class="line"><span class="cl"> node[&#34;currency:BTC&#34;=&#34;yes&#34;]; </span></span><span class="line"><span class="cl"> way[&#34;currency:BTC&#34;=&#34;yes&#34;]; </span></span><span class="line"><span class="cl"> relation[&#34;currency:BTC&#34;=&#34;yes&#34;]; </span></span><span class="line"><span class="cl"> node[&#34;payment:bitcoin&#34;=&#34;yes&#34;]; </span></span><span class="line"><span class="cl"> way[&#34;payment:bitcoin&#34;=&#34;yes&#34;]; </span></span><span class="line"><span class="cl"> relation[&#34;payment:bitcoin&#34;=&#34;yes&#34;]; </span></span><span class="line"><span class="cl">); </span></span><span class="line"><span class="cl">out meta geom; </span></span></code></pre></div><ol start="4"> <li>The latest Overpass response should be saved to <code>data.json</code></li> <li>Here is how you can populate or update the data repository:</li> </ol> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">curl -X POST -d &#34;@query.txt&#34; -o data.json https://overpass-api.de/api/interpreter </span></span></code></pre></div><ol start="6"> <li>Regular updates can be done automatically with the help of GitHub Actions: <a href="https://github.com/bubelov/btcmap-data/blob/main/.github/workflows/refresh-data.yml">https://github.com/bubelov/btcmap-data/blob/main/.github/workflows/refresh-data.yml</a></li> <li>Now we have a self-contained and self-updating repository which caches OSM responses and you can even use it as a backend</li> <li>I&rsquo;d like to avoid making GitHub angry, so I&rsquo;ve set up a Nginx caching proxy on <code>btcmap.org</code> which is supposed to take most of the traffic</li> </ol> <p>This process is pretty simple, transparent and auditable. All you might need is a webserver and curl and both of those programs are available out of the box in most Linux distributions.</p> Static Websites are the Future of Self-Publishing https://bubelov.com/blog/2022/static-websites/ Thu, 19 May 2022 00:00:00 +0000 https://bubelov.com/blog/2022/static-websites/ <p>Sharing your content with the whole world might sound like a solved problem, until you zoom in and start seeing myriads of little cracks in the Matrix.</p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#introduction">Introduction</a></li> <li><a href="#what-is-a-website">What is a Website?</a></li> <li><a href="#what-is-a-static-website">What is a Static Website?</a></li> <li><a href="#why-are-dynamic-websites-more-popular">Why are Dynamic Websites More Popular?</a></li> <li><a href="#how-to-recognize-static-websites">How to Recognize Static Websites</a></li> <li><a href="#static-websites-are-good-for-content-makers">Static Websites are Good for Content Makers</a></li> <li><a href="#static-websites-are-good-for-users">Static Websites are Good for Users</a></li> <li><a href="#how-to-create-static-websites">How to Create Static Websites</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="introduction">Introduction</h2> <p>There is no shortage of platforms which let you publish all kinds of content online. Twitter would gladly let you share your every thought on their website, Instagram can&rsquo;t wait for your next vacation photos to be uploaded and shared with everyone, and there is Blogger for dinosaurs like you and I who can willingly read more than a few lines of text.</p> <p>What does Twitter, Instagram and Blogger have in common? Aside from being big and popular platforms, they all have a power to kick your out of their walled gardens and to delete the content they don&rsquo;t like for any reason.</p> <p>Every content maker wants to be able to easily reach their audience and the web is the best place to share your works. Unfortunately, most content makers don&rsquo;t really know how to publish their stuff online without relying on huge intermediary platforms.</p> <p>Using a publishing platform can simplify your life, but there is no such thing as a free lunch. You and your audience will have to either pay substantial fees or be bombarded with endless ads, needless to say that your every step will be tracked and shared with anyone willing to pay for it. To add insult to injury, you will always be at risk of being de-platformed with no explanation.</p> <p>In this post, I&rsquo;m going to suggest an alternative to being a serf of a huge publishing platform.</p> <h2 id="what-is-a-website">What is a Website?</h2> <p>Let me cite Wikipedia:</p> <blockquote> <p>A website (also written as web site) is a collection of web pages and related content that is identified by a common domain name and published on at least one web server. Examples of notable websites are Google, Facebook, Amazon, and Wikipedia.</p> </blockquote> <p>That&rsquo;s it, every publishing platform is made from the same simple matter. Every website you see in your browser is just a bunch of web pages which are being served by a special program called a webserver.</p> <p>What&rsquo;s a web page, though? It&rsquo;s the thing you&rsquo;re reading now, it&rsquo;s just a text combined with some additional information on how to format it and where to place it. You can right-click on this page in your browser and select &ldquo;View Page Source&rdquo; from the drop-down menu in order to see the whole web page the way your browser sees it.</p> <h2 id="what-is-a-static-website">What is a Static Website?</h2> <p>In a nutshell, a static website is just a bunch of text files. That&rsquo;s actually how the web was meant to operate: let&rsquo;s say you want to visit a website <a href="https://acme.com">https://acme.com</a>, so you type it into an address bar of your browser. Your browser will then fetch a text document from the webserver which is attached to that domain name:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="cp">&lt;!DOCTYPE html&gt;</span> </span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">html</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">head</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">meta</span> <span class="na">charset</span><span class="o">=</span><span class="s">&#34;utf-8&#34;</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">title</span><span class="p">&gt;</span>Acme company<span class="p">&lt;/</span><span class="nt">title</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">head</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">body</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span>Welcome to the Acme company&#39;s website!<span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">body</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">html</span><span class="p">&gt;</span> </span></span></code></pre></div><p>The fact that you received this document doesn&rsquo;t mean that it was stored on a webserver though. Many websites prefer to generate documents on the fly, so they can store only a template like this:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="cp">&lt;!DOCTYPE html&gt;</span> </span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">html</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">head</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">meta</span> <span class="na">charset</span><span class="o">=</span><span class="s">&#34;utf-8&#34;</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">title</span><span class="p">&gt;</span>${company_name}<span class="p">&lt;/</span><span class="nt">title</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">head</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">body</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span>${greeting_message}<span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span>${latest_news_item}<span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">body</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">html</span><span class="p">&gt;</span> </span></span></code></pre></div><p>This looks similar to the first example, but it doesn&rsquo;t contain any actual data. This approach is called <a href="https://en.wikipedia.org/wiki/Dynamic_web_page">dynamic</a> because your webserver will create a document on the fly every time a browser requests a web page. All of those placeholder variables will be replaced with an actual data during the response generation process. Static websites pre-generate all of their pages, so they can avoid doing any heavy work when they need to serve new requests.</p> <p>If dynamic approach sounds like an unnecessary complication, it&rsquo;s because it is. In many real world use cases, you don&rsquo;t need to use a database and an obscure templating engine in order to have your own website and publish your content online. All you need is content and a few lines of HTML and a CSS. Among those things, the hardest one to produce is content, HTML and CSS are trivial to make and adjust.</p> <h2 id="why-are-dynamic-websites-more-popular">Why are Dynamic Websites More Popular?</h2> <p>Some websites are inherently dynamic. Let&rsquo;s say we have a website which has an authentication system with a user profile page, something like <a href="https://acme.com/account">https://acme.com/account</a>. This user account page should show different data to different users. People have different email addresses, profile pics and nicknames, but they all go to a single web page which is expected to be filled with personalized data so every user may get a unique response.</p> <p>That sort of scenario is where dynamic websites shine. Basically, dynamic means unpredictable. When it comes to publishing your writings, photos or even a podcast, there is no need to use dynamic pages. You usually have all the content before you hit &lsquo;publish&rsquo;, so it makes sense to pre-generate your HTML pages and avoid more complex dynamic deployments.</p> <p>There is nothing wrong with using dynamic websites when you really need them. Unfortunately, there are a lot of dynamic websites which don&rsquo;t use nor need dynamic features. By using a dynamic approach when it&rsquo;s unnecessary, you get zero benefits while paying huge extra costs. The thing is, dynamic features aren&rsquo;t free. They can be disastrous for content makers and their audiences alike.</p> <h2 id="how-to-recognize-static-websites">How to Recognize Static Websites</h2> <p>To show you a webpage, your browser sends a request to a webserver associated with its domain name. In response, it gets a bunch of text, and it doesn&rsquo;t really know nor care if this text was pre-generated. As a visitor, you aren&rsquo;t supposed to tell the difference between static and dynamic websites.</p> <p>In practice, it&rsquo;s easy to figure out if a website is static. If it&rsquo;s fast, it&rsquo;s static, if it&rsquo;s slow, it&rsquo;s most likely dynamic. Static websites already have all the answers to all possible questions, so they can quickly return that bunch of text mentioned above, usually stored as <code>.html</code> files somewhere on a webserver. That&rsquo;s the secret of static websites: since all their pages are pre-generated, there is no need to &ldquo;think&rdquo; and generate pages on the fly every time a new request comes.</p> <h2 id="static-websites-are-good-for-content-makers">Static Websites are Good for Content Makers</h2> <p>You need to have a webserver in order to publish HTML documents, but if your pages are dynamic and parts of their content are stored in a database somewhere, you also have to set up and manage that database as well. Complexity is the enemy of resilience and self-sovereignty. Of course, this unnecessary complication leads to unnecessary dependence on various third-party services which promise to make your life a bit simpler. Those services aren&rsquo;t charities, and they have an incentive to keep you dependent on their tools. They also need to be flexible in order to meet the needs of their huge and diverse user base. Don&rsquo;t expect the end result to be optimized for your particular use case.</p> <p>I know a guy who has a website which exposes various financial fraud schemes. Obviously, the fraudsters are dreaming of taking his website down. Luckily for them, it&rsquo;s a dynamic WordPress website, so they flood it with millions of requests from time to time, which is enough to take it down for days. This guy struggled with this problem for a long time and ended up hiring some expensive consultants to help him solve this problem. It didn&rsquo;t solve the problem completely, but it made the attacks more expensive.</p> <p>Exposing fraudsters isn&rsquo;t risk-free, but what about exposing nation states? These entities have tremendous influence, and they don&rsquo;t like when someone lays bare their dirty tricks. Static websites make it easy to resist attempts even when it comes from the most powerful governments.</p> <h2 id="static-websites-are-good-for-users">Static Websites are Good for Users</h2> <p>As I mentioned earlier, one of the surest ways to find out if a website is static is to check how long it takes to load a webpage. Remember, only static websites have all of their pages ready to be served, dynamic websites have to start their routines from scratch every time they receive a new request.</p> <p>In order to generate an HTML document on the fly, a webserver has to query a database and then replace all the placeholders with the data it gets from database query results, which means:</p> <ul> <li>You need to have a database</li> <li>You also need to query that database every time a new request comes in order to prepare a response</li> </ul> <p>All of this makes your life harder and slow down your website. No one likes slow websites, so your audience will thank you if you pick the static option.</p> <h2 id="how-to-create-static-websites">How to Create Static Websites</h2> <p>A static website is just a collection of HTML files and other related resources such as styles, images, videos and so on. HTML pages are text files, so you can create or edit them in your favorite text editor.</p> <p>As your website grows, you might find out that maintaining it is quite a chore. Let&rsquo;s say you have 100 pages, and you need to change an element which is present in 90 of them. First, you need to find those pages, and then you need to manually update all of them. That&rsquo;s usually a point when people start thinking about going dynamic, but having a static website doesn&rsquo;t mean you can&rsquo;t automate repetitive tasks, you just need to use a tool which can provide that automation and generate a static websites for you.</p> <p>There are lots of tools which can help you generate beautiful static websites from a bunch of simple Markdown files. Markdown is the format used by Wikipedia, and I bet most of Wikipedia&rsquo;s contributors don&rsquo;t know how to create HTML documents. Markdown allows authors to focus on their content, and there are plenty of open source tools which can transform Markdown files into HTML documents, so you don&rsquo;t even need to know HTML in order to be able to manage a sizable static website.</p> <h2 id="conclusion">Conclusion</h2> <p>Static websites provide immense benefits for content creators and their audiences alike. In my view, dynamic websites are overused, and it damages the Web. Having your own static website isn&rsquo;t hard, and we should empower and encourage more people to self-publish their content. Knowing that we may be censored affects our behavior, that&rsquo;s why I believe we should move the Web away from centralized platforms and let its users take back control over their digital presence.</p> CLBOSS https://bubelov.com/blog/2022/clboss/ Sun, 08 May 2022 00:00:00 +0000 https://bubelov.com/blog/2022/clboss/ <p>It wouldn&rsquo;t be an exaggeration to say that the success of Bitcoin depends on Lightning Network. This is a level 2 solution which is intended to solve Bitcoin&rsquo;s scalability issues by introducing quite a few clever concepts which every node operator should understand. It&rsquo;s worth noting that all Lightning Network implementations are work in progress, but there are plenty of people who&rsquo;re using them in production without any issues.</p> <p>Managing Lightning node is rather hard and time-consuming, and there is a huge demand for automation. <a href="https://github.com/ZmnSCPxj/clboss">CLBOSS</a> is one of the most interesting developments in that space. This plugin can take control of your Core Lightning node and manage it automatically. It can open and close your channels, and it can also make sure that your inbound and outbound liquidity is well-balanced, which is pretty important for routing nodes. It can also watch for network transaction fees, and it prefers to operate when fees are low. Watching how CLBOSS operates can also be a good learning experience if you want to learn the best practices of Lightning node management.</p> Energy Bill - Part 1 https://bubelov.com/blog/2022/energy-bill-1/ Tue, 03 May 2022 00:00:00 +0000 https://bubelov.com/blog/2022/energy-bill-1/ <p>Gasoline isn&rsquo;t the only thing which is getting more expensive, and I now have to pay almost $0.15 per kWh of electricity, which translates to $200 a month given my current consumption.</p> <p>I got curious if I can bring my power consumption down and what are the effects of my electronic devices on the total bill. Air-cons were the main suspects, but it turned out that the main abuser is LED light. It might seem impossible, given that LED is advertised as a magical power-saving measure. Well, those lights aren&rsquo;t that power-hungry, and they consume about 15W each, but there are 26 LED lights in my house, so it can easily add up to 390 kWh.</p> <p>Next month, I&rsquo;ll run a little experiment where I&rsquo;ll try to use fans instead of air-cons. I will also stop leaving lights on when no one needs them. Will see how it plays out, but my rough calculations are promising:</p> <p><figure> <a href="https://bubelov.com/blog/2022/energy-bill-1/power-consumption_hu_951163f1367c47cb.webp"> <img src="https://bubelov.com/blog/2022/energy-bill-1/power-consumption_hu_55e00dd1a96aa379.webp" alt="" /> </a> <figcaption>Expected power consumption</figcaption> </figure></p> <p>If those numbers are more or less correct, I should expect more than a 5-fold decrease in energy expenses.</p> <p>I remember doing <a href="https://bubelov.com/blog/2022/solar-thailand/">some calculations</a> to figure out the payback period on a solar system in Thailand. Since the energy prices went up, the payback period has to go down, and it did. It turns out there are come upsides in legacy energy sources getting more expensive, it could accelerate our transition to renewable energy. The transition is imminent, and it can be done by the market forces alone, but cheap and widely available government-backed credit can help, I guess.</p> Sleep Schedule https://bubelov.com/blog/2022/sleep-schedule/ Thu, 28 Apr 2022 00:00:00 +0000 https://bubelov.com/blog/2022/sleep-schedule/ <p>It feels really great to wake up at the same time every day. Who knew? I had a random sleep schedule during the last few years and, although is had its benefits, I think that fixed sleep schedule is a better choice, because:</p> <ul> <li>It makes me a bit more productive</li> <li>It gets dark very early in Thailand so waking up early allows me to get more natural sunlight</li> <li>My dog appreciates fixed schedule since dogs are creatures of habit, and they like to know when they will get their food or go for a walk</li> </ul> Better Call Saul - Season 6 https://bubelov.com/blog/2022/better-call-saul-6/ Wed, 27 Apr 2022 00:00:00 +0000 https://bubelov.com/blog/2022/better-call-saul-6/ <p>I watched only the first three episodes so far, and they were as good as I expected them to be. Apart from the great story, there is something catchy and breathtaking in the filming technique used in this movie. I really enjoy when a camera picks a single small and seemingly insignificant object and shows that even those little corners of the scene are full of life. Those moments are beautiful, and I wonder if they have any special meaning.</p> Twitter https://bubelov.com/blog/2022/twitter/ Tue, 26 Apr 2022 00:00:00 +0000 https://bubelov.com/blog/2022/twitter/ <p>Looks like there was a lot of drama since Elon Musk announced that he&rsquo;s going to buy Twitter. I&rsquo;m not a big fan of his, but I honestly don&rsquo;t see why it is a bad thing. In fact, I&rsquo;m pretty sure Twitter would become a better place with Musk in charge. Twitter in its current state is a disaster, and it desperately needs a lot of quick and radical changes. We&rsquo;ll see how it plays out, but I&rsquo;m cautiously optimistic.</p> News App Progress https://bubelov.com/blog/2022/news-app-progress-04/ Sat, 23 Apr 2022 00:00:00 +0000 https://bubelov.com/blog/2022/news-app-progress-04/ <p>My feed reader app is ripe for a major refactoring, mostly thanks to a lot of new features added by me and other contributors. The goal is to improve code readability and test coverage. I&rsquo;m also planning to improve my standalone web feed parser.</p> <p>The most painful thing to refactor is the way the app deals with feed and entry links. The current approach includes assumptions about the number and the nature of those links, which conflicts with both the spec and the real world. I think it&rsquo;s time to make things right.</p> PipeWire https://bubelov.com/blog/2022/pipewire/ Fri, 22 Apr 2022 00:00:00 +0000 https://bubelov.com/blog/2022/pipewire/ <p>I know next to nothing about Linux audio stack, but it&rsquo;s really hard not to notice <a href="https://wiki.archlinux.org/title/PipeWire">PipeWire</a>. It has been gaining a lot of traction recently, so I&rsquo;ve decided to check it out.</p> <p>As usual, ArchWiki is the best place to start reading about Linux software. I installed all the main PipeWire-related packages and everything seems to work like a charm. I was able to amend audio device properties with <code>wireplumber</code>, and it allowed me to disable an audio output device and a mic I never use, which helped me de-clutter audio settings UI. Having less devices enabled also helps the system to fall back to proper audio devices when I disconnect my headset.</p> Mastodon https://bubelov.com/blog/2022/mastodon/ Wed, 20 Apr 2022 00:00:00 +0000 https://bubelov.com/blog/2022/mastodon/ <p>Mastodon devs have finally released an official Android client a few days ago, which could make Mastodon more appealing to a wider audience. Truth to be told, Mastodon is much less addictive than Twitter, for various reasons, so it&rsquo;s going to be really hard for the new users to stick with it. It&rsquo;s not like we have any open alternatives though, so it might be worth the effort.</p> Meditations https://bubelov.com/blog/2022/meditations/ Thu, 14 Apr 2022 00:00:00 +0000 https://bubelov.com/blog/2022/meditations/ <p>This book is considered one of the cornerstones of the Stoic philosophy and this is certainly a must-read for anyone who&rsquo;s interested in this school of thought. I heard a lot of good things about Meditations and many people like to re-read it dozens and even hundreds of times, and now I understand why. This book is a pleasant read, although some of its parts are pretty intense, so I&rsquo;d advise anyone to take their time and not to rush through this book. Checking out Marcus Aurelius bio also helps to better understand the context.</p> <p>After reading through a few takes on the history of philosophy, I&rsquo;m planning to read some actual philosophical works. Meditations seemed to be a good start, and that&rsquo;s why I chose to read it.</p> The Sinner https://bubelov.com/blog/2022/the-sinner/ Mon, 04 Apr 2022 00:00:00 +0000 https://bubelov.com/blog/2022/the-sinner/ <p>This movie is pretty dark and unpredictable, just what I expect a classic detective movie to be. Some episodes are a bit boring, but it&rsquo;s a great movie in general.</p> S4 / Hibernation https://bubelov.com/blog/2022/s4-hibernation/ Tue, 29 Mar 2022 00:00:00 +0000 https://bubelov.com/blog/2022/s4-hibernation/ <p>I was curious to see the performance impact of S4 Linux sleep mode, so I made a few tests. It turns out, resuming from hibernation is <strong>slower</strong> than a cold boot! I was shocked to see that, but a cold boot is 2 seconds faster than returning from &ldquo;suspend to disk&rdquo; mode. I&rsquo;m not sure if it&rsquo;s even possible to speed it up, so I&rsquo;ve decided to stop using swap. Since the cold boot is no slower than returning from hibernation, I just set my laptop to power off when its lid is closed.</p> Cyberpunk 2077 https://bubelov.com/blog/2022/cyberpunk-2077/ Mon, 28 Mar 2022 00:00:00 +0000 https://bubelov.com/blog/2022/cyberpunk-2077/ <p>I like this game, but it doesn&rsquo;t seem to be well-balanced or re-playable. The graphics is great, the story is interesting, and it doesn&rsquo;t feel too scripted and limiting, but for some reason, I lost interest in this game as soon as I finished its main story.</p> IELTS https://bubelov.com/blog/2022/ielts/ Sun, 27 Mar 2022 00:00:00 +0000 https://bubelov.com/blog/2022/ielts/ <p>Having an up-to-date IELTS certificate is one of the requirements you need to meet in order to apply for a permanent residence in Canada. Getting a good score is also crucial if you want to maximize your chances of being approved. Needless to say, it&rsquo;s important to prepare for such a test. Knowing what to expect gives you an advantage, and there are also some simple tips and tricks which need to be memorized in order to get a good score.</p> <p>This test is no joke, and even a native English speaker can get a low score, so it&rsquo;s certainly not enough just to know the language. You really need to understand how IELTS operates and how to better demonstrate your English ability. I&rsquo;m planning to prepare for a couple of months, at least. My listening and reading skills are good enough, but my writing and speaking skills certainly need to be improved.</p> Gnome 42 https://bubelov.com/blog/2022/gnome-42/ Fri, 25 Mar 2022 00:00:00 +0000 https://bubelov.com/blog/2022/gnome-42/ <p>One of the coolest things about Arch is the ability to use the latest versions of Linux software such as GNOME shell. GNOME 42 is amazing, it introduces built-in dark theme, and it also allows fractional scale factor to be set above 200%. I really missed the ability to set the scale factor to 250%, because that&rsquo;s what required to properly size GNOME on my XPS laptop. New screenshot flow looks modern, and it&rsquo;s much easier to use. I really like the direction the GNOME project is heading.</p> Arch Linux https://bubelov.com/blog/2022/arch/ Thu, 24 Mar 2022 00:00:00 +0000 https://bubelov.com/blog/2022/arch/ <p>I&rsquo;m starting to recover from war-induced apathy, and I got my feet wet with Arch Linux. The actual experience wasn&rsquo;t that different from what I expected from Arch: the whole setup process was done from the command line and the documentation was top-notch. I learned a ton of new low-level things while I was playing with the different configurations, and I&rsquo;m planning to use Arch as a daily driver on my Dell XPS 13 9380. Everything works like a charm so far, although <code>pacman</code> is quite an odd piece of software, and I&rsquo;ll need some time to learn its quirks.</p> Civilization VI https://bubelov.com/blog/2022/civilization-6/ Mon, 07 Mar 2022 00:00:00 +0000 https://bubelov.com/blog/2022/civilization-6/ <p>It&rsquo;s really hard to match Civilization VI in terms of re-playability. It still feels challenging and unpredictable if the difficulty level is set slightly higher than normal. Playing this game at high difficulty means starting with a serious disadvantage to other civilizations, so you can&rsquo;t really afford to be too relaxed and sloppy in executing your strategy.</p> Elden Ring https://bubelov.com/blog/2022/elden-ring/ Thu, 03 Mar 2022 00:00:00 +0000 https://bubelov.com/blog/2022/elden-ring/ <p>As a huge fan of Bloodborne, I couldn&rsquo;t resist buying a copy of Elden Ring on Steam. That wasn&rsquo;t easy due to sanctions, but I managed to change my account country to Thailand by adding a local payment card. The game feels satisfyingly familiar, but it also offers a few new gameplay elements such as mounted fights and stealth. I didn&rsquo;t finish this game yet, but I certainly will.</p> Buying a Grill and Cooking at Home https://bubelov.com/blog/2022/grill/ Sat, 29 Jan 2022 00:00:00 +0000 https://bubelov.com/blog/2022/grill/ <p>I somehow managed to survive for 31 years without learning how to cook. Cooking always struck me as something complicated, arcane and unscientific. Taste is subjective, right? There were always plenty of cafes and restaurants around me, and nowadays, there is also a bunch of food delivery apps, so relying on that infrastructure seemed like a division of labor in action: isn&rsquo;t it better to let the specialists do all those things? I entertained the idea of home cooking a few times in the past, but I never really acted on it.</p> <p>A few weeks ago, I stayed in a national park in central Thailand. It&rsquo;s a really nice place, and I was able to rent a cozy tiny house nearby a river. This place didn&rsquo;t have access to restaurants and food delivery services, but it had a basic grill and a bunch of coals, so I was kind of forced to use it. Nothing fancy, just some meat from my cooler box. This little adventure has sparked my interest in cooking at home, and I started looking for a decent grill suitable for beginners. I also wanted my future grill to be compact enough to take it with me on my next trip.</p> <p>I asked around and everyone agreed that I should buy a Weber grill. Weber seems to be the market leader, and it has a few cheap and compact charcoal grills. Some people prefer gas grills, but they are more expensive, and I don&rsquo;t mind the hassle of using coal. Jumbo Joe seemed like an ideal model, except that it lacks embedded thermometer. Thermometers are super handy, and I really wanted to have one. Luckily for me, Asian variants of Jumbo Joe are slightly different from US ones, and they actually have built-in thermometers.</p> <p><figure> <a href="https://bubelov.com/blog/2022/grill/grill_hu_fdad3208e7cac89f.webp"> <img src="https://bubelov.com/blog/2022/grill/grill_hu_2d796dfab44819d8.webp" alt="" /> </a> </figure></p> <p>Once my new grill arrived, I couldn&rsquo;t wait to test it. So far I tried:</p> <ul> <li>Steaks</li> <li>Sausages</li> <li>Burgers</li> <li>Corn</li> <li>Potatoes</li> </ul> <p>Cooking those things turned out to be surprisingly easy and the taste wasn&rsquo;t bad at all. I also tried to &ldquo;reverse-engineer&rdquo; my favorite dish from the local bar: fermented sausages. I found a pack of sausages which tasted exactly like the ones from that bar, for 25% of the price. It turns out, preparing food isn&rsquo;t as hard as I thought it may be. It&rsquo;s also much more scientific than I expected: it&rsquo;s all about controlling heat and how it interacts with different foods in different environments.</p> <p>Reading a manual was also insightful, and it&rsquo;s really a kind of rabbit hole, I have many ideas which I want to test and experiment with.</p> Solar Panels in Thailand https://bubelov.com/blog/2022/solar-thailand/ Tue, 25 Jan 2022 00:00:00 +0000 https://bubelov.com/blog/2022/solar-thailand/ <p>I noticed a significant year-on-year increase in the amount of solar panels during my last car trip across Thailand, so I started wondering if going solar is finally became economically viable. Spoiler: it is, but there are some caveats. A 3 kW solar system without battery would cost you about $3,500, and it can cover about 35% of a total power consumption assuming you live in a small house with 3 air-cons, and you work from home so at least one or two of them are always on. For a bigger house, 5 kW system might be more appropriate, and it would cost you about $5,000.</p> <p>Producing more energy than you consume is currently pointless because the odds are that your energy company won&rsquo;t buy it from you, so you need to store your extra energy somewhere. Batteries are very expensive, so I won&rsquo;t even consider this route. My house consumes about 1,000 kW/h of electricity monthly, so a 3 kW solar system is expected to cut my bill by 350 kW/h. Assuming $0.12 per kW/h tariff, the monthly savings would amount to $42, so such a system would pay off in about 7 years. Your mileage may vary, but that seems to be the more or less realistic expectation. It would also be wise to add solar panel maintenance cost (cleaning), which is about $100+ a year.</p> <p>So, the solar is great and promising, but it&rsquo;s still pretty expensive. I don&rsquo;t see a reason to switch to solar now, but I hope it will get cheaper in the future. The price of solar panels has decreased dramatically in the last few years, continuing a decades-long trend.</p> Second Moderna Shot https://bubelov.com/blog/2022/moderna/ Thu, 20 Jan 2022 00:00:00 +0000 https://bubelov.com/blog/2022/moderna/ <p>I expected a second Moderna shot to have fewer side effects, but it wasn&rsquo;t the case at all. I felt like shit for a couple of days, but all the side effects vanished as suddenly as they initially appeared. Not sure if I want to get a third shot though. It seems pointless, at least for now, while we don&rsquo;t have any vaccines effective against the dominant Omicron strain.</p> Keychron K8 https://bubelov.com/blog/2022/keychron-k8/ Wed, 19 Jan 2022 00:00:00 +0000 https://bubelov.com/blog/2022/keychron-k8/ <p>I&rsquo;ve bought three mechanical keyboards recently, and my favorite so far is the Keychron K8 with Gateron brown switches. It took me some time to get used to the switches, but I think they&rsquo;re pretty good. That doesn&rsquo;t mean I won&rsquo;t experiment with others. I really want to try different Cherry switches as well as custom keycaps.</p> <p>The only thing I don&rsquo;t like about the K8 is its closed‑source firmware. But it looks like Keychron will start selling an <a href="https://www.keychron.com/pages/keychron-k8-pro-qmk-wireless-mechanical-keyboard">open‑source K8</a> pretty soon.</p> <p>I don&rsquo;t think I&rsquo;ll buy it, because I&rsquo;m more or less happy with the current model, and I can probably flash some open‑source firmware on it anyway.</p> My Disillusionment in Russia https://bubelov.com/blog/2022/my-disillusionment-in-russia/ Wed, 12 Jan 2022 00:00:00 +0000 https://bubelov.com/blog/2022/my-disillusionment-in-russia/ <h2 id="context">Context</h2> <p>To a lot of foreigners, Russia is a puzzle. I&rsquo;ve met plenty of people who dislike it, but just as many who admire it, so I&rsquo;ve always been curious about what drives such strong feelings either way. Personally, I can relate to both sides. But often, whether you love Russia or hate it, those feelings are built on ideas that don&rsquo;t really match reality.</p> <p>Emma Goldman fell victim to early Soviet propaganda to such an extent that she moved to Russia, believing the Bolsheviks were building a Marxist utopia. She was a sharp woman, and it didn&rsquo;t take her long to realize that post‑revolutionary Russia wasn&rsquo;t paradise, its new rulers turned out to be even more cruel, dishonest, and vicious than their predecessors in the early years of Soviet rule.</p> <h2 id="quotes">Quotes</h2> <p>Overall, it&rsquo;s an outstanding book. Here are a few quotes I found particularly interesting:</p> <blockquote> <p>Two years of earnest study, investigation, and research convinced me that the great benefits brought to the Russian people by Bolshevism exist only on paper, painted in glowing colours to the masses of Europe and America by efficient Bolshevik propaganda.</p> </blockquote> <blockquote> <p>Another circumstance which perplexed me was that the markets were stacked with meat, fish, soap, potatoes, even shoes, every time that the rations were given out. How did these things get to the markets? Everyone spoke about it, but no one seemed to know.</p> </blockquote> <blockquote> <p>He preferred silence. Secondly, there was no medium of expression in Russia itself. To protest to the Government was useless. Its concern was to maintain itself in power. It could not stop at such &ldquo;trifles&rdquo; as human rights or human lives. Then he added: “We have always pointed out the effects of Marxism in action. Why be surprised now?”</p> </blockquote> <blockquote> <p>It never occurred to them that the purpose of a revolution is merely to cause a transfer of possessions⁠—to put the rich into the hovels and the poor into the palaces. It was not true that the workers have gotten into the palaces. They were only made to believe that that is the function of a revolution. In reality, the masses remained where they had been before. But now they were not alone there: they were in the company of the classes they meant to destroy</p> </blockquote> <blockquote> <p>A small political party trying to control a population of 150,000,000 which bitterly hated the Communists, could not hope to maintain itself without such an institution as the Cheka. The latter was characteristic of the basic principles of Bolshevik conception: the country must be forced to be saved by the Communist Party.</p> </blockquote> <blockquote> <p>The new economic policy turned Moscow into a vast marketplace. Trade became the new religion. Shops and stores sprang up overnight, mysteriously stacked with delicacies Russia had not seen for years. Large quantities of butter, cheese, and meat were displayed for sale; pastry, rare fruit, and sweets of every variety were to be purchased.</p> </blockquote> <blockquote> <p>The means employed become, through individual habit and social practice, part and parcel of the final purpose; they influence it, modify it, and presently the aims and means become identical.</p> </blockquote> <h2 id="summary">Summary</h2> <p>I find this book historically accurate, but I think the author misses the bigger picture. Pre‑revolutionary Russia was deeply mismanaged, and for a long time, the Communists, along with other opposition movements, were the victims, not the perpetrators.</p> <p>Things flipped after the revolution, but you&rsquo;d have to be naive to think radical change can ever be painless. Sure, plenty of mistakes were made, but the blame for Soviet Russia&rsquo;s starting point lies squarely with the old Tsarist regime.</p> <p>Look at where the country ended up: a global power with a decent standard of living and a high degree of self‑sufficiency, despite constant efforts by other powers to keep it contained. That&rsquo;s not nothing. However you feel about their methods, it&rsquo;s hard to deny the Bolsheviks did a largely effective job of pulling the nation out of the dire neglect it suffered under the late monarchy.</p> Wasteland 3 https://bubelov.com/blog/2022/wasteland-3/ Tue, 11 Jan 2022 00:00:00 +0000 https://bubelov.com/blog/2022/wasteland-3/ <p>I finally got enough time to beat this game. As I noticed in a previous post, it&rsquo;s pretty great. Well, at least it&rsquo;s good enough to consume almost a hundred hours of my time, so be warned. Like with many ambitious RPG games, its overall balance isn&rsquo;t that good, and the game itself is pretty buggy. I really enjoyed the storyline though, and both official DLCs are great and full of amusing plot twists.</p> <p>Sadly, there aren&rsquo;t many financial incentives to fix many annoying bugs in a traditional commercial game development model. It&rsquo;s nice that some really great games get picked up and reverse-engineered by the open source community. The most successful example is probably OpenMW, a remake of Morrowind. It&rsquo;s exceptionally stable, and the practice to fix bugs in commercial games on non-commercial basis isn&rsquo;t unheard of. The original Vampire: The Masquerade was notoriously buggy, bit it still gets patches from the community, almost two decades since its initial release.</p> PinePhone Keyboard https://bubelov.com/blog/2022/pinephone-keyboard/ Sun, 02 Jan 2022 00:00:00 +0000 https://bubelov.com/blog/2022/pinephone-keyboard/ <p><a href="https://www.pine64.org/2021/12/31/happy-new-year-the-keyboard-and-cases-are-here/">PinePhone keyboard</a> looks really promising, and it can also triple PinePhone&rsquo;s lifespan on a single charge. I wouldn&rsquo;t hurry to buy it though, since software support is still lacking.</p> Road Trips https://bubelov.com/blog/2021/road-trips/ Thu, 30 Dec 2021 00:00:00 +0000 https://bubelov.com/blog/2021/road-trips/ <p>I was never a car guy, but I&rsquo;m starting to enjoy some aspects of car ownership, such as being able to have long car trips with reasonable comfort. The comfort thing required some adjustments like installing a tonneau cover and finding a way to keep my food and drinks cold. There a plenty of car fridges on the market, but they tend to be either bad or expensive. The one I was recommended to buy is currently priced at almost $2,000. That&rsquo;s a good motivation to look for the alternative and cheaper options, such as portable cooler boxes. I must admit, I didn&rsquo;t have high expectations when I ordered a $100 locally produced cooler box, but this thing actually works, and it works surprisingly well. It can keep stuff cool for a few days while laying in the back of a truck driving in a tropical climate. It looks like Some road trip comforts can be pretty cheap.</p> Vineyards in Thailand https://bubelov.com/blog/2021/thai-vine/ Wed, 29 Dec 2021 00:00:00 +0000 https://bubelov.com/blog/2021/thai-vine/ <p>I like drinking vine occasionally, although I usually drink beer. There are a few decent breweries in Thailand, but I didn&rsquo;t really expect to stumble on a vineyard here, which is exactly what happened a few days ago.</p> <p><figure> <a href="https://bubelov.com/blog/2021/thai-vine/vine-1_hu_f753a916e76b118.webp"> <img src="https://bubelov.com/blog/2021/thai-vine/vine-1_hu_5d6150ddbcfa1a54.webp" alt="" /> </a> </figure></p> <p>It&rsquo;s not a big vineyard, but it&rsquo;s in operation for more than 20 years already, and it looks like it&rsquo;s totally possible to make a good vine in tropics. I liked the vine selection enough to order a couple of bottles.</p> <p><figure> <a href="https://bubelov.com/blog/2021/thai-vine/vine-2_hu_e189a2ee2127a33d.webp"> <img src="https://bubelov.com/blog/2021/thai-vine/vine-2_hu_99018f7d4bd5900f.webp" alt="" /> </a> </figure></p> <p>Those grapes will be ripe in February, but no one really knows how long the plant will last. In Europe, grape can easily live for over a hundred years, but we don&rsquo;t really have any data for Thailand.</p> Moderna https://bubelov.com/blog/2021/moderna/ Mon, 27 Dec 2021 00:00:00 +0000 https://bubelov.com/blog/2021/moderna/ <p>Finally, I was able to get my first Moderna shot. There were a few side effects, but nothing serious so far. Finding a second shot will be quite an adventure, maybe I will need to go to another city or buy someone else&rsquo;s place in a queue.</p> Mechanical Keyboards https://bubelov.com/blog/2021/mechanical-keyboards/ Sat, 25 Dec 2021 00:00:00 +0000 https://bubelov.com/blog/2021/mechanical-keyboards/ <p>We can&rsquo;t really take it for granted that market forces will always improve the quality of every product. There is a compromise to be made between product price and product quality, and that&rsquo;s exactly what happened with keyboards. My first PC was a Pentium 4 with a Genius keyboard. They served me well for many years, and it kind of became a baseline for evaluating all computers and peripherals in the future. Newer CPUs were always better, SSDs felt like magic compared to HDDs, GPUs kept improving. We had a lot of incremental improvements pretty much everywhere.</p> <p>The keyboards didn&rsquo;t change that much though, and it&rsquo;s not because there is nothing left to improve. Sure, laptops had to be more creative with space, and they popularized so-called &ldquo;scissor&rdquo; switches, which actually feels like a noticeable improvement, but it&rsquo;s nothing to write home about. In the past few years, I noticed that some of my friends started to mention mechanical keyboards, so I decided to give those things a try. It turned out, there are three kinds of mechanical key switches: linear, tactile and clicky, and the right choice is a matter of personal preference. It&rsquo;s generally a good idea to try all of them and decide which one is more comfortable for you. Personally, I had to buy two keyboards with different switches because it was hard to make a final decision.</p> <p>My initial impression is very good, and I&rsquo;m thinking about buying this keyboard in the future:</p> <p><a href="https://system76.com/accessories/launch">https://system76.com/accessories/launch</a></p> <p>It&rsquo;s a bit pricey, but it&rsquo;s open source, and it&rsquo;s Linux-first.</p> Game: Civilization 6 https://bubelov.com/blog/2021/civilization-6/ Thu, 23 Dec 2021 00:00:00 +0000 https://bubelov.com/blog/2021/civilization-6/ <p>I&rsquo;m glad to see that new Civilization games had managed to keep their core DNA while improving and innovating where necessary. The balance feels a bit odd, but it was always like that, and it seems to be an unsolvable issue.</p> PinePhone Batteries https://bubelov.com/blog/2021/pinephone-batteries/ Mon, 20 Dec 2021 00:00:00 +0000 https://bubelov.com/blog/2021/pinephone-batteries/ <p>I needed an extra battery for my PinePhone, and I was surprised that it works fine with Samsung J7 batteries which can be ordered for $3 apiece here in Thailand.</p> SD Card Lifespan https://bubelov.com/blog/2021/sd-card-lifespan/ Sun, 19 Dec 2021 00:00:00 +0000 https://bubelov.com/blog/2021/sd-card-lifespan/ <p>I had to install a few CCTV cameras recently, and all of them had SD card slots. Although SD cards are super convenient, they aren&rsquo;t known to be durable. Debugging a system with failing SD card is hell, since SD cards tend to die slowly and silently. Tools like <code>f3</code> might help, but storage is usually the least expected suspect when the system starts to misbehave, and I ended up spending hours trying to figure out what&rsquo;s wrong with my system before checking its SD card. The lack of TBW counters and other metadata doesn&rsquo;t help either.</p> <p>That said, I really wanted to find a durable SD card, because writing video 24/7 is a tough workload for those little things. It turns out, there are plenty of so-called &ldquo;durable&rdquo; and &ldquo;industrial grade&rdquo; SD cards on the market. They&rsquo;re relatively expensive, and they usually slower than the usual A1-graded cards, which means that you have to sacrifice random IO performance in order to get higher sequential IO endurance. Apparently, there is no such thing as a universal SD card. They all may look the same and have the same size, but it&rsquo;s very important to pick the one which is suitable for your particular workload.</p> Far Cry https://bubelov.com/blog/2021/far-cry/ Sat, 18 Dec 2021 00:00:00 +0000 https://bubelov.com/blog/2021/far-cry/ <p>The first Far Cry game which I beat was Far Cry 5 and I enjoyed it. It also sparked my interest in this whole series, so I&rsquo;ve decided to start from the very beginning: the original Far Cry. The game aged well, and it also runs smoothly on Linux in 4K. The graphics looks a bit dated indeed, but it doesn&rsquo;t look too ancient. I remember trying this game in school, but I got bored after a few minutes of playing the original Far Cry.</p> <p>Probably, something has changed in my game preferences since that time. I never really liked FPS games, so that might explain why I wasn&rsquo;t interested in the first games from this series. Far Cry 5 is barely a shooter, it&rsquo;s more of a mix of many genres. The original Far Cry feels more like a classical FPS: no open world, no weapon mods, no commerce or RPG elements, no crafting, etc. In other words, it&rsquo;s a bit more limited than the games I used to play, but it was a great experience nevertheless.</p> Steam Link https://bubelov.com/blog/2021/steam-link/ Thu, 16 Dec 2021 00:00:00 +0000 https://bubelov.com/blog/2021/steam-link/ <p>I&rsquo;m looking for a good replacement for my PS4, and buying PS5 or Xbox isn&rsquo;t an option due to their closed nature and user-hostile policies. Steam Deck isn&rsquo;t shipping yet and cloud streaming has even more issues than the platforms like PS and Xbox.</p> <p>Streaming by itself sounds like a good idea though, but only if it&rsquo;s done from my own hardware and if the network latency is low. That&rsquo;s precisely what Steam Link is doing, so I decided to give it a try. It has a client for my Android TV, and it took me just a few minutes to connect it to my Linux PC. I need more time to figure out if it can be a good replacement for consoles but the initial results are promising.</p> Pixel 6 and Android 12 https://bubelov.com/blog/2021/pixel-6-android-12/ Tue, 14 Dec 2021 00:00:00 +0000 https://bubelov.com/blog/2021/pixel-6-android-12/ <p>I miss a bottom navigation panel, but otherwise Android 12 running on Pixel 6 feels pleasant. The camera is superb as usual, the battery life is average, and the screen is pretty good.</p> Wasteland 3 https://bubelov.com/blog/2021/wasteland-3/ Thu, 09 Dec 2021 00:00:00 +0000 https://bubelov.com/blog/2021/wasteland-3/ <p>I didn&rsquo;t play previous Wasteland games, so I had no historical expectations from Wasteland 3. Luckily for me, this game has a lot of things I like, such as post-apocalyptic setting, turn-based combat, RPG elements, open world and endless quests filled with dark humor. I didn&rsquo;t beat it yet, but I&rsquo;m looking forward to doing that after my road trip is over.</p> Debunking Proof of Work FUD https://bubelov.com/blog/2021/pow-fud/ Tue, 30 Nov 2021 00:00:00 +0000 https://bubelov.com/blog/2021/pow-fud/ <p>One of the criticisms of Bitcoin mining and proof of work schemes in general is their perceived high energy use. Putting aside the subjectivity of defining the right uses of energy, there are also a lot of dishonest rhetorical tricks some people use in order to prove their points. Here is a <a href="https://marginalrevolution.com/marginalrevolution/2021/11/bitcoin-and-electricity.html">great article</a> written by an economist which neatly and patiently debunks most of those attacks.</p> Notes on Bitcoin Mining https://bubelov.com/blog/2021/bitcoin-mining/ Mon, 29 Nov 2021 00:00:00 +0000 https://bubelov.com/blog/2021/bitcoin-mining/ <p>Everyone heard about Bitcoin, but it&rsquo;s hard to find someone who can explain how it works. A relative of mine wanted to try bitcoin mining, and he asked me to help him with setting things up. I only have a high-level grasp of Bitcoin&rsquo;s inner workings, so it turned out to be quite a venture.</p> <p>First, it&rsquo;s pretty hard to figure out if bitcoin mining is profitable and if it&rsquo;s a good investment. There are many variables at play, and most of them are unpredictable. Here is a few things I found interesting:</p> <ul> <li>Mining is far more profitable than I expected.</li> <li>Cost of electricity is important, but you can still mine with a good profit even if your electricity is rather expensive.</li> <li>ASICs are noisy, you can turn on your vacuum cleaner to get a glimpse on an expected noise level from a single device.</li> <li>You&rsquo;re going to need a good ventilation if you want to mine bitcoins.</li> <li>ASICs are pretty durable if you treat them well, 5+ year lifespans isn&rsquo;t something unheard of.</li> <li>ASICs are liquid, you can easily sell them even if you used them for quite some time.</li> <li>ASICs are super easy to set up, you just need to tell them where to send the money, and you&rsquo;re good to go.</li> </ul> Material 3 https://bubelov.com/blog/2021/material-3/ Sat, 27 Nov 2021 00:00:00 +0000 https://bubelov.com/blog/2021/material-3/ <p>Material is a well known cross-platform design system made by Google. It looks pretty good in web apps, but mobile apps is where it really shines. Recently, Google had released a third iteration of its Material system for Android and I decided to give it a go in one of my apps. The first impressions are pretty good so far. It looks cleaner and more lightweight. Also, I&rsquo;m glad it remained as fast as the previous version. Unlike Jetpack Compose, native Material components are actually usable and don&rsquo;t take my phone many seconds to load.</p> <p>One of the most distinctive features of Material 3 is user-customizable color palette. I hope it will push some mobile apps towards using less brand colors. Seriously, people hate not being in control, and not allowing them to override colors seems like a bad idea. I removed all the custom colors from my app, and I have a good feeling about it. At least, no one complained yet, but we&rsquo;ll see how it goes. I didn&rsquo;t actually test it on a real device yet, but I will soon be able to play with this theme on Pixel 6. I tested it on the latest emulator images though, but they&rsquo;re both terribly broken and thus not representative.</p> ATOM RPG: Trudograd https://bubelov.com/blog/2021/trudograd/ Wed, 24 Nov 2021 00:00:00 +0000 https://bubelov.com/blog/2021/trudograd/ <p>The <a href="https://bubelov.com/blog/2019/atom-rpg/">review</a> of ATOM RPG is one of the oldest posts in my blog. To summarize, the original game was pretty good, it&rsquo;s basically some kind of Slavic Fallout. Trudograd is a completely standalone game which takes place in the same post-apocalyptic world. The story is completely new and most of the game systems were improved in a big way. There are also fewer issues with the game difficulty and balance. I&rsquo;m looking forward to playing future games from the ATOM team.</p> Calibre https://bubelov.com/blog/2021/calibre/ Tue, 23 Nov 2021 00:00:00 +0000 https://bubelov.com/blog/2021/calibre/ <p>Finding a decent e-book reader software is not an easy endeavor, bit it looks like I found a reader which suits my needs pretty well. It&rsquo;s called Calibre and, of course, it&rsquo;s open source. I can&rsquo;t say I liked the default layout and theme, but they ended up to be easily customizable. Book library UI is a bit outdated though, but it&rsquo;s not a big deal for me since I spend most of my time reading stuff and not browsing the library.</p> Metro Exodus https://bubelov.com/blog/2021/metro-exodus/ Sat, 20 Nov 2021 00:00:00 +0000 https://bubelov.com/blog/2021/metro-exodus/ <p>I&rsquo;m not a big fan of FPS, but I really enjoyed Metro Exodus. One of the wow-things wasn&rsquo;t even game-related, it was the fact that I could launch an AAA level game on Linux without any issues. The graphics is pretty good and the story is bearable. Gun mod systems and RPG elements seem to be all the rage nowadays, probably that&rsquo;s why I started to like some FPS games. Far Cry 5 did the same, basically, and I enjoyed it too.</p> Hellbound https://bubelov.com/blog/2021/hellbound/ Sat, 13 Nov 2021 00:00:00 +0000 https://bubelov.com/blog/2021/hellbound/ <p>Another month, another hyped up Korean movie. It&rsquo;s definitely worth watching, in my opinion, but don&rsquo;t expect anything extraordinary. I like how this movie portrays religious cults and their irrational and hypocritical lines of thought. I wouldn&rsquo;t say the &ldquo;good&rdquo; guys in this movie are likable, but the &ldquo;bad&rdquo; guys are pretty hate-worthy, that&rsquo;s for sure. This movie features heavy and noticeable CGI, which seems to be a common theme in Korean movies. Hellbound closely resembles Sweet Home in that regard. The monsters look absurd and fake, but somehow it doesn&rsquo;t put off the viewer.</p> Narcos: Mexico https://bubelov.com/blog/2021/narcos-mexico/ Tue, 09 Nov 2021 00:00:00 +0000 https://bubelov.com/blog/2021/narcos-mexico/ <p>The new season is as great as the old ones. Narcos isn&rsquo;t really a kind of movie people will remember for years, I could hardly recall what happened in previous seasons. It&rsquo;s not necessarily a bad thing, though. I mean, I still enjoyed watching the new season, and that&rsquo;s the only thing that matters. Honestly, I&rsquo;m not even sure if I even remember much of what I just saw, but that&rsquo;s fine.</p> 7 Days to Die https://bubelov.com/blog/2021/7-days-to-die/ Fri, 05 Nov 2021 00:00:00 +0000 https://bubelov.com/blog/2021/7-days-to-die/ <p>This game seems to be in an infinite alpha stage. I tend to check it every year or so, and it always has something new and interesting, but it&rsquo;s about as far from a release quality as it was many years ago. The game is actually great, though. I used to play it on my PS4 last time, but it looks like console versions have been abandoned for quite some time. PC build appears to be maintained, and it slowly gains new features. It also has a decent Linux support too.</p> Squid Game https://bubelov.com/blog/2021/squid-game/ Fri, 29 Oct 2021 00:00:00 +0000 https://bubelov.com/blog/2021/squid-game/ <p>It was impossible to avoid all the hype around Squid Game. I&rsquo;m not sure if this movie lives up to that hype, but I found it pretty interesting. I wouldn&rsquo;t say the plot is unique, but this movie is made with effort, and it shows. Interestingly, there is a movie on Netflix called Alice in Borderland, which looks strikingly similar to Squid Game. This movie is much older, but it looks like that it never gained much traction outside Japanese market. Makes me wonder if I missed something really distinctive about Squid Game.</p> <p>Another interesting thing about Squid Game is its production costs. There is a consensus among movie producers that US had become too expensive to make movies. Many are saying that Netflix can cut its production costs by a few times by moving production overseas. I think it&rsquo;s a great news, and I hope that the movie making will become more geographically and culturally diverse.</p> Midnight Maas https://bubelov.com/blog/2021/midnight-maas/ Thu, 28 Oct 2021 00:00:00 +0000 https://bubelov.com/blog/2021/midnight-maas/ <p>This movie is pretty odd, and it&rsquo;s certainly not for everyone. I almost stopped watching after the first episode, but it became more interesting as the story progressed. Personally, I like that it approaches &ldquo;vampire stories&rdquo; genre from a new and unique angle, and I also like how it portrays the life in a poor American fishing village. I&rsquo;m a bit tired of the movies about middle and upper classes of American society, and, as a person who never lived in US, I&rsquo;m more interested in its darker and grimmer side, which is surprisingly shy of a public eye.</p> Speeding Up F-Droid https://bubelov.com/blog/2021/speeding-up-fdroid/ Wed, 27 Oct 2021 00:00:00 +0000 https://bubelov.com/blog/2021/speeding-up-fdroid/ <p>I spent a good number of years working on Android apps, mostly because I believe that mobile platforms should be open, and Android has long been the only viable option. Android is the only thing that keeps the market from getting back to the dark ages of closed platforms. That said, some parts of Android ecosystem are horrible. For instance, it&rsquo;s really hard to distribute your apps via Google Play, and things are only getting worse. With a closed platform, developers and users alike have no choice but to accept the rules set by a vendor, and that&rsquo;s exactly where the open source nature of Android gives us much needed leverage which allows us to fix the things we don&rsquo;t like.</p> <p>Google Play is a sad place which regularly bans good apps for bullshit reasons while having no problem with apps filled with trackers and all kinds of dark patterns. Fortunately, we don&rsquo;t really need Google Play in order to use Android. Some people think that Google Play is the only way to install apps, but it&rsquo;s mostly a result of (probably criminal) conspiracy by Google and friends to fend off their competitors. Every Android user can use an alternative store and one of those alternatives is <a href="https://f-droid.org/">F-Droid</a>.</p> <p>I like to think of F-Droid as of Android&rsquo;s package manager such as <code>pacman</code> or <code>apt</code>. It&rsquo;s basically a collection of open source software built and maintained by a group of volunteers. I have an app there, and my experience with this repository has been great so far. As a developer, I love it. The problems start when I try to recommend it to more users. The most important thing an app store can do is to allow users to quickly discover and install apps. Unfortunately, F-Droid is extremely bandwidth-constrained, and it&rsquo;s practically impossible to download apps, unless you&rsquo;re living in Europe in proximity to their servers.</p> <p>Why is F-Droid so slow and unreliable? It turns out, it doesn&rsquo;t use a CDN for its static assets. It&rsquo;s a very easy problem to solve, and it can have a huge impact on F-Droid adoption rate. It looks like the maintainers realize the severity of this problem, and they have a solution in the works:</p> <p><a href="https://gitlab.com/fdroid/admin/-/issues/229">https://gitlab.com/fdroid/admin/-/issues/229</a></p> <p>I tried a CDN-backed repo, and it was blazing fast. Hopefully, CDN will soon become a default option for F-Droid.</p> Jitsi https://bubelov.com/blog/2021/jitsi/ Sun, 24 Oct 2021 00:00:00 +0000 https://bubelov.com/blog/2021/jitsi/ <p>Gone are the days when remote work was seen as something extraordinary. I never had any doubts that remote work will prevail, but I&rsquo;m not entirely sure if out infrastructure is ready for that transition. There are plenty of fundamental questions which haven&rsquo;t been answered yet, such as: how should we communicate when we work remotely most of the time? Text is king, and we have some nice email and chat software. WhatsApp and Telegram need to go, but luckily we have Matrix, Signal and other trustworthy software that can be used by companies and individuals alike.</p> <p>Text is essential, but our remote workflows also require some form of audio and video communication. I honestly don&rsquo;t understand why, but Zoom seems to be the most popular choice when it comes to virtual meetings and even one-on-one calls. We can do much better than that, and, frankly, we absolutely should. People who&rsquo;re willing to install non-auditable apps made by shady Chinese companies are either incompetent or naive.</p> <p>That said, we really need to have an easy to use, open and auditable alternative to Zoom. Recently, I&rsquo;ve been hearing a lot of good things about <a href="https://jitsi.org/">Jitsi</a>, so I decided to give it a go. Jitsi is an open source software which doesn&rsquo;t even need to be installed because it&rsquo;s a browser app. It has a ton of features and its user interface is pretty smooth and modern-looking.</p> Europa Universalis IV https://bubelov.com/blog/2021/eu4/ Thu, 21 Oct 2021 00:00:00 +0000 https://bubelov.com/blog/2021/eu4/ <p>I just bought a reasonably fast GPU, and I&rsquo;m back to PC gaming. EU IV is one of my favorite games, and it runs natively on Linux. This time, I&rsquo;ve decided to play for native Americans in order to find out a bit more about their civilisation and history. I haven&rsquo;t made contact with the Old World yet, but I&rsquo;m sure it will be hard to survive this encounter.</p> Firefox Shows Ads https://bubelov.com/blog/2021/firefox-shows-ads/ Thu, 21 Oct 2021 00:00:00 +0000 https://bubelov.com/blog/2021/firefox-shows-ads/ <p>Well, Firefox can&rsquo;t stop finding new ways to alienate its users. It is no wonder it loses its market share at a staggering pace. This time, Firefox management decided to show ads in the address bar. I mean, I get it: Firefox needs to break its dependence on Google&rsquo;s money, but adding ads kind of defeats the purpose of the whole project. I hope Firefox team will recognize it before it&rsquo;s too late.</p> Steam on Linux https://bubelov.com/blog/2021/steam-on-linux/ Thu, 21 Oct 2021 00:00:00 +0000 https://bubelov.com/blog/2021/steam-on-linux/ <p>Of course, the best way to test a new GPU is to play some games. Gaming have long been a Windows-exclusive activity, but things are changing at a rapid pace. Installing Steam on Linux is even easier than doing the same thing on Windows, but that&rsquo;s not the main thing which stopped Linux gaming for a long time. The main problem is: a lot of games aren&rsquo;t cross-platform, and they choose to target Windows exclusively. Things are getting better, but it&rsquo;s unlikely that we ever reach 100% coverage with Linux, unless we find a way to run Windows games on Linux. It&rsquo;s not an easy task, but <a href="https://www.protondb.com/">Proton</a> team works really hard on that, and it shows.</p> <p>Today, a little more than 50% of my Steam library is officially supported on Linux. Frankly, it&rsquo;s enough games to keep me busy for the rest of my life, but the trend is clear. As Proton gets better, we will be able to play pretty much any game on Linux, even if its own developers didn&rsquo;t plan for that. I&rsquo;m pretty sure 90%+ is doable in the next few years. Personally, I stopped using a separate Windows environment for gaming because most games I like to play are working great on Linux.</p> AMD RX 6600 XT https://bubelov.com/blog/2021/amd-rx-6600-xt-2/ Tue, 19 Oct 2021 00:00:00 +0000 https://bubelov.com/blog/2021/amd-rx-6600-xt-2/ <p>Finally, I was able to get a GPU for my new PC. I&rsquo;ve been <a href="https://bubelov.com/blog/2021/amd-rx-6600-xt/">waiting for a long time</a> for this specific model, and it works like a charm with my current setup. One of the main benefits of AMD GPUs is the fact that their drivers are baked into the Linux kernel, so they are expected to work out of the box with zero additional setup. Nvidia refuses to open their drivers, and they&rsquo;re lagging on a few important fronts, such as Wayland support. Also, AMD is an underdog, so why not support more competition in the GPU space?</p> Desktop Motherboard Firmware Updates https://bubelov.com/blog/2021/desktop-mb-firmware-updates/ Sat, 16 Oct 2021 00:00:00 +0000 https://bubelov.com/blog/2021/desktop-mb-firmware-updates/ <p>It turns out, updating motherboard firmware is no fun. I&rsquo;ve been using Dell XPS 13 for a few years, and it can handle firmware updates automatically, so I never had to think about it. Desktop motherboards seem to operate in a different way, though. About a week ago, I found out that my new GIGABYTE motherboard has an outdated firmware. To add insult to injury, many updates I missed included various improvements for systems equipped with my CPU.</p> <p>How hard is it to update desktop firmware? Well, I had to find a flash drive, format it to legacy FAT filesystem and then do a few manual adjustments in BIOS. None of those missed updates had any critical fixes, but it would be naive to dismiss the importance of being able to update firmware quickly and easily. There is certainly a room for improvement in the desktop segment.</p> Static Linking https://bubelov.com/blog/2021/static-linking/ Thu, 30 Sep 2021 00:00:00 +0000 https://bubelov.com/blog/2021/static-linking/ <p>What makes software like Miniflux or Syncthing so great? It&rsquo;s always tempting to find a single cause when we&rsquo;re trying to analyze a complex phenomenon. Modern software is complex, but it doesn&rsquo;t mean all its complexity is necessary. There are ways to reduce software complexity without compromising on features or quality, and that&rsquo;s what good programs do. Let&rsquo;s take Syncthing as an example. It&rsquo;s distributed as a single binary which you can copy to any computer sharing the same ISA and an operating system, and it will work just fine.</p> <p>Now, compare this approach with installing thousands of scripts, setting up a container runtime, orchestration tools and a reverse proxy. By the way, that&rsquo;s what you need to do in order to properly install Nextcloud. Software which praises self-sovereignity should be easy to setup, otherwise it&rsquo;s no more than a marketing bluff.</p> Miniflux https://bubelov.com/blog/2021/miniflux/ Wed, 29 Sep 2021 00:00:00 +0000 https://bubelov.com/blog/2021/miniflux/ <p>I use web feeds to discover what&rsquo;s new in the world. Most websites which are worthy of your attention have their own feeds, and you can easily add them to your feed reader, which allows you to merge all of your content sources in a single timeline. By the way, that&rsquo;s exactly what <a href="https://github.com/bubelov/news">my Android app</a> does.</p> <p>In many cases, having a feed reader app isn&rsquo;t enough though. Some people have a few devices, and they want them to be in sync. Let&rsquo;s say I already read some news from an Android app, and now I want to finish reading today&rsquo;s portion of news from my laptop. My Android app should let my laptop know that I already read some news, and I don&rsquo;t want them to be shown again. Those kinds of problems can be solved by setting up your own server and connecting all your apps to this common source of truth.</p> <p>Initially, my app supported only one kind of server: Nextcloud News. It works reasonably well, but it&rsquo;s pretty slow and hard to setup. Recently, I&rsquo;ve been made aware of a server called <a href="https://miniflux.app/">Miniflux</a> and it turned out to be the best feed server I&rsquo;ve ever seen. It&rsquo;s ridiculously simple to setup and it&rsquo;s also blazing fast, so I decided to support Miniflux in the next release of my app.</p> Syncthing https://bubelov.com/blog/2021/syncthing/ Mon, 27 Sep 2021 00:00:00 +0000 https://bubelov.com/blog/2021/syncthing/ <p>Finding a good open source replacement for Dropbox/iCloud/whatever is hard. We&rsquo;ve been using files for ages, but we don&rsquo;t really have any good, reliable and widely adopted tools and standards for sharing files between devices. Personally, I&rsquo;ve been using Nextcloud for file sync for the last few years, but I was never satisfied with its performance and reliability. <a href="https://syncthing.net/">Syncthing</a> has caught my attention because it promised ease of use, ease of deployment and stellar performance. That sounded almost too good to be true, so I&rsquo;ve decided to give it a go.</p> <p>This Syncthing thing turned out to be an amazing piece of software. First, you don&rsquo;t even need a server in order to use it. It can sync your data between your devices in a direct way, which allows Syncthing to target and empower a much larger audience. I&rsquo;m also impressed by the error handling flow in Syncthing. It doesn&rsquo;t try to hide any errors, and it does a good job at explaining what&rsquo;s going on, and why. Another interesting thing about Syncthing is the fact that it&rsquo;s operated by a <a href="https://syncthing.net/foundation/">non-profit</a>. It&rsquo;s not completely server-less, and sometimes your devices need a proxy in order to punch the NAT. In this case, Syncthing will contact a proxy server for help. That may sound as a security issue, but you don&rsquo;t really need to trust proxies since all traffic is TLS encrypted.</p> XPS 13 Dynamic Backlight Shenanigans https://bubelov.com/blog/2021/xps-13-dynamic-backlight/ Tue, 21 Sep 2021 00:00:00 +0000 https://bubelov.com/blog/2021/xps-13-dynamic-backlight/ <p>I had a strange issue with my Dell laptop for a while: sometimes it would randomly mess with the display brightness whenever I moved my cursor. It was odd and curious, but it didn&rsquo;t happen often enough to justify investigating.</p> <p>A few weeks ago, I accidentally found the cause. Turns out, Dell has a feature called dynamic backlight control, and it doesn&rsquo;t always work as intended. Fortunately, it can be disabled in the BIOS.</p> Debian 11 - First Impressions https://bubelov.com/blog/2021/debian-11-2/ Sun, 19 Sep 2021 00:00:00 +0000 https://bubelov.com/blog/2021/debian-11-2/ <p>I&rsquo;ve been running Debian 11 with KDE for a month now and my first impressions are good. Debian is still true to its reputation of a ridiculously stable Linux distro. It works great out of the box, and it needed very little tuning for my use cases. I have a pretty exotic networking setup with both wired and wireless networks active at the same time, so I needed to make sure different kinds of traffic have different priorities. Setting this up was pretty easy and there it&rsquo;s a well documented process.</p> USB Everywhere https://bubelov.com/blog/2021/usb-everywhere/ Wed, 15 Sep 2021 00:00:00 +0000 https://bubelov.com/blog/2021/usb-everywhere/ <p>Two good USB-related events this month:</p> <ul> <li>EU plans to force all device manufacturers to adopt USB-C.</li> <li>USB accessories will soon have a special markings, making it easy to figure out their capabilities.</li> </ul> <p>It&rsquo;s sad that Apple provoked more regulations, other market participants seem to converge on USB-C voluntarily. Anyway, this regulation is good for iPhone users and for the environment. I think it&rsquo;s mostly a failure of antitrust regulations though.</p> The Open Society and its Enemies https://bubelov.com/blog/2021/open-society/ Mon, 13 Sep 2021 00:00:00 +0000 https://bubelov.com/blog/2021/open-society/ <p>Science is an amazing tool, but there&rsquo;s no real agreement on how it actually works. That question bothered a man named Karl Popper enough that he spent most of his life thinking about it. I knew of Popper from history of philosophy courses, but it turns out he had a lot of interesting thoughts on other topics as well.</p> <p>Like many great thinkers of his era, Karl Popper was forced to grapple with the roots of evil, it&rsquo;s hard not to when you watch Hitler&rsquo;s rise to power in real time.</p> <p>Finance has long been one of my main interests, and nearly everyone in that space knows the name George Soros. Why bring him up in a text about science, Popper, and Hitler? It turns out this trio has more in common than you might think.</p> <p>Popper spent his career searching for the origins of oppressive regimes. George Soros, the financier, is one of his biggest admirers, not just a fan, but a major funder of ideas through his <a href="https://www.opensocietyfoundations.org/">Open Society Foundations</a>.</p> <p>That name might sound familiar if you follow politics. Several governments, especially those framed as having authoritarian leanings, like Hungary, are loudly hostile toward Soros and his projects. Hate is a strong signal, it acknowledges a perceived threat. I wanted to understand what that threat really is.</p> <p>The Open Society and Its Enemies is an attempt to identify the ideas that gave rise to history&rsquo;s most brutal regimes. Popper locates the core of the problem in a mindset he calls <strong>&ldquo;historicism&rdquo;</strong>, the belief that societies are conscious entities moving toward a predetermined end-state. In his view, accepting this premise normalizes the erosion of individual freedom and ultimately leads to catastrophic, oppressive outcomes.</p> <p>Some find it absurd to link Nazis and socialists, but is it possible they share a fundamental way of viewing history and a common &ldquo;ends justify the means&rdquo; mentality? According to Karl Popper, <strong>absolutely</strong>.</p> Hitman 2 https://bubelov.com/blog/2021/hitman-2/ Tue, 07 Sep 2021 00:00:00 +0000 https://bubelov.com/blog/2021/hitman-2/ <p>This game is created by assholes, and they don&rsquo;t really try to hide it. It&rsquo;s exploitative all the way down the line. It starts with forcing you to accept a bogus privacy policy. I declined, so it disabled most of in-game content until I change my mind. I didn&rsquo;t, but it kept reminding me of those missed features all the time. Well, it is only a first dark pattern I stumbled upon, the next one is even more sinister. It turns out, Hitman 2 is not a self-contained game but just a launcher for a bunch of add-ons which you need to buy in order to fully experience this game. Sony shouldn&rsquo;t really allow such garbage in their store.</p> <p>Apart from that, the gameplay is OK and the graphics is nice. I enjoyed a couple of &ldquo;free&rdquo; levels. It&rsquo;s much easier than the original Hitman, but it can be challenging at times. I got this game for free via PS Plus, so I have no regrets. I wouldn&rsquo;t ever pay for such a game though.</p> Bloodborne https://bubelov.com/blog/2021/bloodborne-2/ Sat, 04 Sep 2021 00:00:00 +0000 https://bubelov.com/blog/2021/bloodborne-2/ <p>Finally, I managed to beat the main Bloodborne storyline, and I must say, that&rsquo;s one of the best games I played in a long time. It&rsquo;s also highly replayable, so I&rsquo;m going to try a New Game Plus mode. Bloodborne also has a separate set of locations called chalices, and I didn&rsquo;t finish all of them yet. This game does a good job at triggering all kinds of emotions from extreme anger to extreme joy, all thanks to its tough boss fights. They are often complex and unpredictable, which goes against the modern trend of making games easy to play. A typical AAA game makes sure that you never get stuck, so Bloodborne treatment can be surprising and even irritating for many players, but the joy of winning a tough battle can easily outweigh many hours or even days of endless defeats.</p> Docker Compose and Systemd https://bubelov.com/blog/2021/docker-compose-systemd/ Sat, 28 Aug 2021 00:00:00 +0000 https://bubelov.com/blog/2021/docker-compose-systemd/ <p>There are many good reasons why Docker Compose is popular among developers and self-hosting enthusiasts. It may lack some features of a &ldquo;world scale&rdquo; orchestrator, but it&rsquo;s much easier to use and configure than something like Kubernetes. I tended to use Docker Compose pretty much everywhere up until recently, but now I prefer Systemd. It may sound like comparing apples to oranges, but Systemd can do a lot of things Docker Compose does in a much cleaner fashion.</p> <p>The thing is: if you have a Linux server, it&rsquo;s probably based on Systemd, and there might be no good reasons to install any additional dependencies. Avoiding extra dependencies isn&rsquo;t the only reason to stick with Systemd, though. Generally, installing two invasive systems competing to do the same thing is a recipe for a disaster. Many Docker Compose users are unaware about the fact that it messes with their firewalls, and many learned about it the hard way. Up until recently, even installing Docker Compose on something not too mainstream, like Raspberry Pi, was so nuanced and confusing that it deserved a special post in this blog. Finally, it&rsquo;s impossible to avoid Systemd nowadays, but it&rsquo;s pretty easy to avoid Docker Compose, so why clutter your mind with two things when you can get away with just one?</p> <p>Docker itself (without Compose) is very handy when you need to deploy something extremely complicated (often, for no good reason). For instance, Nextcloud is essentially a bunch of PHP scripts which require a lot of dependencies and some painful manual setup. It could have been a simple executable, but it&rsquo;s not. That&rsquo;s where Docker shines: it allows us to hide messy to deploy software behind a simple CLI. Setting things up is a hard job which might need deep expertise. Docker can abstract that away, just give it a port to bind to, and it&rsquo;ll take care of the rest. Go and Rust have set a good trend of packing everything into a single binary, which obsoletes many of the Docker use cases, especially for the typical single-node self-hosted deployments. I hope more software will be distributed like this in the future, but for now, it&rsquo;s pretty much impossible to avoid Docker.</p> Debian 11 https://bubelov.com/blog/2021/debian-11/ Thu, 26 Aug 2021 00:00:00 +0000 https://bubelov.com/blog/2021/debian-11/ <p>I have to confess, most of my servers and workstations run Ubuntu, but I always have at least a single Debian machine. One of the features that tipped the scale to the Ubuntu side is the lack of proper WireGuard support in Debian 10. I also have a fleet of Raspberry Pi servers, and they&rsquo;re all Ubuntu-based, since it&rsquo;s the only properly supported server distro (Raspberry Pi OS is too niche, and it mostly focused on education). I don&rsquo;t think I&rsquo;ll migrate my Pi&rsquo;s to Debian any time soon, bit I&rsquo;m planning to give it a go on my main laptop and desktop.</p> <p>Speaking of my desktop, it has a Zen 3 CPU and Debian 11 comes with a kernel v5.10, which is the first kernel with the full Zen 3 support. That was a close call, but it looks like it&rsquo;s going to work just fine, and it won&rsquo;t have any missing features.</p> vim https://bubelov.com/blog/2021/vim/ Sun, 22 Aug 2021 00:00:00 +0000 https://bubelov.com/blog/2021/vim/ <p>My vim journey progresses at a slow but steady pace, and I still have mixed feelings about it. It really changed the way I code and the way I experience the whole process. I guess those changes are mostly due the fact that I don&rsquo;t use code completion. It makes me think about code on a lower, more mechanical level, which is surprisingly exciting, at least for the first time.</p> <p>Another thing which pleased me was my laptop&rsquo;s battery life. GUI-based IDE&rsquo;s tend to consume far more power, but I can easily code in vim all day long on a single charge. I still have a lot to learn, but I&rsquo;m already productive enough not to feel that using vim slows me down in any significant way. I&rsquo;m planning to learn new vim tricks at a much slower and relaxed pace from now on.</p> SQLite Strict Column Typing https://bubelov.com/blog/2021/sqlite-strict-column-typing/ Fri, 20 Aug 2021 00:00:00 +0000 https://bubelov.com/blog/2021/sqlite-strict-column-typing/ <p>I love SQLite because maintaining a complicated client-server database is a lot of work for little to no gain in projects that don&rsquo;t need to deal with high loads and multi-node deployments.</p> <p>If you never worked with SQLte, you might be surprised how fast and scalable it actually is. Unfortunately, it can also be surprising in much less pleasant ways. For instance, SQLite didn&rsquo;t support <code>RENAME COLUMN</code> till the beginning of 2021.</p> <p>I&rsquo;m glad it&rsquo;s fixed now, but the biggest remaining problem with SQLite, in my opinion, is the lack of strict typing. It has this obscure concept of type affinity, and it allows you to insert data of any type into column of any type.</p> <p>It sounds bad, and it&rsquo;s really as bad as it sounds. That&rsquo;s why I&rsquo;m super excited about the introduction of <a href="https://www.sqlite.org/draft/stricttables.html">STRICT Tables</a>. Hopefully, dynamic column types will soon become a thing of the past.</p> PlayStation https://bubelov.com/blog/2021/playstation/ Mon, 16 Aug 2021 00:00:00 +0000 https://bubelov.com/blog/2021/playstation/ <p>Just a reminder on why closed hardware and software shouldn&rsquo;t exist. I have PS4 and its Wi-Fi module started to misbehave. I didn&rsquo;t pay much attention to it, because I don&rsquo;t play online games anyway. Well, it looks like my console wants to contact Sony&rsquo;s licencing servers every fifteen minutes and, if it fails to do that, it just kicks me out of my games. I ended up switching to wired connection, but I won&rsquo;t ever buy nor recommend any Sony garbage, at least until they stop treating their paying users as fucking criminals.</p> The Demon-Haunted World https://bubelov.com/blog/2021/demon-haunted-world/ Sun, 15 Aug 2021 00:00:00 +0000 https://bubelov.com/blog/2021/demon-haunted-world/ <p>Science is a beating heart of a modern world, but some people believe that it&rsquo;s in decline now. That would be disastrous, if true. There is no shortage of conspiracy theories telling us stories of some shadowy groups which hide some essential knowledge from the public eye. It looks like the reality is even more cynical: all the necessary knowledge is public, but finding it is like finding a needle in a haystack. We&rsquo;re constantly bombarded with all kinds of information and filtering all the noise, lies, errors, biases and speculations have never been as important as it is today.</p> <p>Carl Sagan tried to popularize scientific thinking in an attempt to prevent the world from diving back into the dark ages it emerged from not so long ago. This book tells us a lot of stories about science and pseudo-science, and it expands on how hard it might be to separate one from another. Personally, I share many of the author&rsquo;s concerns. It&rsquo;s hard not to worry about the decline of science and rationality when people burn 5G towers, refuse life-saving vaccines and fall victims of populist politicians en masse. I&rsquo;m not even talking about uneducated populations of some third-world countries. Those things seem to be on the rise everywhere.</p> <p>To summarize, I think this book is still relevant despite its respectable age. I&rsquo;m looking forward to reading more books by Carl Sagan.</p> Blockstream Talk https://bubelov.com/blog/2021/blockstream-talk/ Wed, 11 Aug 2021 00:00:00 +0000 https://bubelov.com/blog/2021/blockstream-talk/ <p>Blockstream is one of the most recognized names in the Bitcoin space, and I don&rsquo;t think any other company can match them in terms of added value to the whole ecosystem. Those guys keep contributing code to Bitcoin repo, and they&rsquo;re also heavily involved in building infrastructure around Bitcoin. There is a reason why this company is valued at a few billion USD, after all.</p> <p>One of the recent Blockstream contributions to the Bitcoin community is their new podcast. The first episode features Adam Back, and it&rsquo;s focused on the history of ideas and technology behind Bitcoin. This episode isn&rsquo;t technical, the conversation focuses on the origin story of Bitcoin and the reasons why it was invented. It also covers how it works, especially compared with its predecessors, but don&rsquo;t expect many nitty-gritty details on that. Ironically, Adam Back would be one of the best folks to ask about those details, but I&rsquo;m now sure it&rsquo;s the trajectory this podcast wants to pursue. Unfortunately, this podcast doesn&rsquo;t seem to have a website and a standalone feed, so here is a <a href="https://www.youtube.com/watch?v=b7QT6km2hGs">YouTube link</a>.</p> Bloodborne https://bubelov.com/blog/2021/bloodborne/ Thu, 05 Aug 2021 00:00:00 +0000 https://bubelov.com/blog/2021/bloodborne/ <p>I&rsquo;ve been playing Bloodborne before, but I didn&rsquo;t manage to beat it yet. Now I&rsquo;m already reached the last location, but I decided to explore chalice dungeons before triggering the end of game. Well, I&rsquo;m just scared of trying it again, so I decided to level up my character first. Anyway, this game is really cool and its extremely re-playable. It reminds me Diablo series, and I wish Diablo 3 was more like Bloodborne. The main difference is the source of re-playability. Diablo games are focused on trying different characters and &ldquo;builds&rdquo;, but there is no such thing in Bloodborne. Instead, the main cause of excitement is higher uncertainty when it comes to fight. In Bloodborne, even the weakest enemies can easily kill you, if you make a mistake. It encourages players to stay sharp and engaged. Also, collecting items is less addictive in Bloodborne, and it feels like a good thing.</p> On Ethics https://bubelov.com/blog/2021/on-ethics/ Fri, 30 Jul 2021 00:00:00 +0000 https://bubelov.com/blog/2021/on-ethics/ <p>People keep asking unanswerable questions since the ancient times, and we tend to label such questions as philosophical. One of the fundamental puzzles of our lives is ethics. Every living being acts, but humans are a bit more introspective. How do we know what to do? How do we make sure we&rsquo;re doing the right things? What&rsquo;s the difference between good and bad actions? Is this goodness objective or subjective?</p> <p>My own understanding of ethics is pretty simplistic, but that simplicity makes it practical. If we assume that avoiding suffering is more important than seeking pleasure, it&rsquo;s not hard to infer consistent set of rules for judging actions. Here are a few examples:</p> <ul> <li><strong>Should I steal?</strong> No, it harms other people, and it can also harm you if you get caught.</li> <li><strong>Should I seek knowledge?</strong> Yes, knowledge of causes and effects helps us prevent suffering.</li> <li><strong>Should I help others?</strong> It depends. If there are people in need who suffer a lot, and you don&rsquo;t see helping them as a huge personal sacrifice, you should help them. If people are doing OK without your help or if your help can lead to unintended long-term harm, it may be unethical to help others despite their wishes and even proven short-term relief. Consider some welfare programs or rent control. They might look as a good thing aimed to eliminate suffering, but you can easily do more harm than good, if you don&rsquo;t examine the data carefully and critically enough. Good actions tend to rely on cold reason, so we should prefer reason to emotions when the stakes are high.</li> <li><strong>Should I work at Facebook?</strong> No, absolutely not. Companies of this kind promise to add pleasure but end up causing a lot of suffering. This also applies to monopolies in general. Concentrated power also concentrates the potential to abuse it, so it&rsquo;s no good.</li> <li><strong>Can I be rude with some people?</strong> If you feel it&rsquo;s the best way to prevent them from harming you or themselves. Otherwise, no.</li> <li><strong>Should I have debt?</strong> Borrowing money allows us to buy nice things here and now, but we should be confident of our ability to pay back our debts. Little debt on good terms can be good, but it&rsquo;s very important not to underestimate the worst case scenario.</li> </ul> <p>This little rule helps me answer many polarizing and difficult questions.</p> Tor Switches to Rust https://bubelov.com/blog/2021/tor-rust/ Sat, 24 Jul 2021 00:00:00 +0000 https://bubelov.com/blog/2021/tor-rust/ <p>Writing software is hard, mostly because the expectations are rather high. We want our software to be cheap, safe, fast, reliable, easy to use, and that&rsquo;s just a few things to start with. There is also a reason why we have so many tools and languages: different software projects have different sets of priorities.</p> <p>What do we want from a project like <a href="https://www.torproject.org/">Tor</a>? Well, we want everything, but, if we have to prioritize, safety comes on the top of the list. That&rsquo;s why I&rsquo;m very excited to hear that Tor developers have <a href="https://blog.torproject.org/announcing-arti">decided</a> to embrace Rust. Here is what they&rsquo;re saying:</p> <blockquote> <p>For us, these problems mean that programming in C is a slow and painstaking process. Everything we write takes more code than we&rsquo;d like it to, and we need to double-check even the safest-looking code to make sure it doesn&rsquo;t fall prey to any of C&rsquo;s list of enormous gotchas. This slows us down seriously, and increases the cost of adding new features.</p> </blockquote> <blockquote> <p>Rust seems like the clearest way out of our bind. It&rsquo;s a high-level language, and significantly more expressive than C. What&rsquo;s more, it&rsquo;s got some really innovative features that let the language enforce certain safety properties at compile-time. To a first approximation, if the code compiles, and it isn&rsquo;t explicitly marked as &ldquo;unsafe&rdquo;, then large categories of bugs are supposed to be impossible.</p> </blockquote> <blockquote> <p>That&rsquo;s a huge win for us in programming and debugging time, and a huge win for users in security and reliability.</p> </blockquote> Steam Deck https://bubelov.com/blog/2021/steam-deck/ Thu, 22 Jul 2021 00:00:00 +0000 https://bubelov.com/blog/2021/steam-deck/ <p>I&rsquo;m not a fan of mobile gaming, mostly because touch controls are awful and the average business model of a mobile game is pretty scammy. There is nothing wrong with mobile gaming per se, and Nintendo Switch is a good example of an amazing mobile gaming platform, both hardware and software-wise. I own a Switch and my only problem with it is the fact that it&rsquo;s a closed product. Nintendo did a good job with Switch, but we can do better.</p> <p>Steam is probably the biggest game distribution platform, but it&rsquo;s not a monopolist, and it has a good track record of fair play. It looks committed to openness and interoperability, and its actions speak better than its words. This month, Valve presented its unique take on mobile gaming hardware, and I like what I see.</p> <p><a href="https://www.steamdeck.com/en/">Steam Deck</a> will have a conventional x86 architecture, it will run Linux, and it will be able to run pretty much any game from your Steam library. Crucially, Steam Deck won&rsquo;t force you to use Steam software. You don&rsquo;t need to own a Steam Deck in order to be able to enjoy what&rsquo;s coming. The technology which is responsible for running all Windows games on Linux is open source, so you can use it in any way you find suitable, on any hardware.</p> <p>Open platforms are good, but why would anyone buy a Steam Deck and use it for something other than playing Steam games? Here is a real world use case I&rsquo;m actually looking forward to. It looks like nothing will stop me from installing something like <a href="https://retropie.org.uk/">RetroPie</a> on a Steam Deck, which can make it an ultimate retro meta-console able to play thousands of classic games at least up to PS1.</p> <p>Aside from more exotic use cases, bringing all Windows games to Linux is no small feat, I&rsquo;m looking forward to buying this thing.</p> Final Fantasy VII Remake https://bubelov.com/blog/2021/final-fantasy-7/ Wed, 21 Jul 2021 00:00:00 +0000 https://bubelov.com/blog/2021/final-fantasy-7/ <p>Full disclosure: I&rsquo;m a huge Final Fantasy fan.</p> <p>One of my favorite games of all times is Final Fantasy X. I also played parts I, II, and a Tactics spin-off, all of them are on the top of the list of the best games I ever played. It is no wonder that I fell in love with Final Fantasy VII Remake pretty much instantly. The game is very well-made: the story is solid, the graphics is beautiful and the gameplay is addictive. This remake squeezes every bit of performance it could from my PS4, making it roar like a jet engine (putting on headphones helps with that issue).</p> <p>I had a book called World of Final Fantasy when I was a child. That explains why some scenes feel oddly familiar. I probably read about this game, but I didn&rsquo;t have a chance to play it. I&rsquo;m not even sure if it was available on PS1. Anyway, this game is awesome, and I enjoyed every single moment of its gameplay.</p> Framework Laptop https://bubelov.com/blog/2021/framework-laptop/ Sun, 18 Jul 2021 00:00:00 +0000 https://bubelov.com/blog/2021/framework-laptop/ <p>One of the things I learned the hard way is the importance of preferring easily repairable hardware, especially when it comes to laptops. It&rsquo;s also nice to be able to upgrade my SSD or add more RAM. I&rsquo;m currently using Dell XPS 13, and I bought it precisely because it&rsquo;s super easy to upgrade and repair such a laptop. I already replaced its battery once, and I&rsquo;m planning to equip it with a latest-gen SSD in the near future. Dell did a good job with this laptop, but we can do better. <a href="https://frame.work/">Framework Laptop</a> looks like it takes extensibility and repairability to the next level.</p> <p>Replaceable RAM is a radical departure from the rest of the laptop market, and I wonder if it&rsquo;s going to backfire. Some initial reviewers point that the battery life isn&rsquo;t that good, at least for now. Maybe it&rsquo;s a price of non-soldered RAM? I&rsquo;m not a hardware expert, but I hope that this product succeeds and sets the trend for the whole market.</p> AMD RX 6600 XT https://bubelov.com/blog/2021/amd-rx-6600-xt/ Fri, 16 Jul 2021 00:00:00 +0000 https://bubelov.com/blog/2021/amd-rx-6600-xt/ <p>I can&rsquo;t wait for this GPU shortage craze to be over. so I can buy a decent AMD GPU. Nvidia isn&rsquo;t Linux-friendly, and I&rsquo;d prefer to get rid of it as soon as possible. <a href="https://www.amd.com/en/press-releases/2021-07-29-amd-radeon-rx-6600-xt-graphics-card-sets-new-standard-for-high-framerate">AMD RX 6600 XT</a> is the newest addition to the high-end line of AMD GPUs. It&rsquo;s kind of the lowest-end model of a highest-end product line, to be precise, which makes it a sweet spot for my desktop. I don&rsquo;t use my desktop for gaming, but I may play a game or two in the future, so I have no objection to having all the latest and greatest GPU features on the market for the lowest possible price.</p> Red Dead Redemption 2 https://bubelov.com/blog/2021/rdr-2/ Wed, 07 Jul 2021 00:00:00 +0000 https://bubelov.com/blog/2021/rdr-2/ <p>This game feels like a low-effort TV series in a sense that it&rsquo;s too stretched and artificial. The story is OK, the graphics is good, but the only truly great thing about this game is the atmosphere of Wild West. RDR 2 motivated me to look up a lot of stuff relevant to that period, and the game progresses is so slow with so little action that it was possible to look things up comfortably right on the spot. I wouldn&rsquo;t recommend this game unless you want to look at an expensive attempt to portray United States circa 1899.</p> US Antitrust Bills https://bubelov.com/blog/2021/us-antitrust/ Wed, 30 Jun 2021 00:00:00 +0000 https://bubelov.com/blog/2021/us-antitrust/ <p>It looks like the US government <a href="https://www.nasdaq.com/articles/u.s.-panel-approves-five-antitrust-bills-break-em-up-bill-up-next-2021-06-24">starts to realize</a> the harm done by big tech companies, which confirms my optimistic outlook on the future of tech industry. I remember times when companies like Apple, Google, and even Facebook offered a lot of value. Things started to change during the last decade. Slowly but surely, we started getting less value while paying ever-increasing direct and indirect costs.</p> <p>How is that possible? Well, that&rsquo;s what happens when a few companies monopolize and divide the market. They turned their users into serfs, and they tax the hell out of millions of developers, kicking out anyone who dares to resist or even complain. That&rsquo;s not healthy, but it&rsquo;s nothing new. It&rsquo;s what you get it you allow monopolies to thrive and break the rules. Luckily, we already have antitrust laws, so let&rsquo;s modernize them and make them work for public benefit.</p> Getting Rid of Bad Knowledge https://bubelov.com/blog/2021/bad-knowledge/ Tue, 29 Jun 2021 00:00:00 +0000 https://bubelov.com/blog/2021/bad-knowledge/ <p>Bad habits are hard to break. The first step is to realize that some of your actions are wrong and things can be and should be improved, but it&rsquo;s not the hardest part. Staying at your &ldquo;local maximum&rdquo; is very appealing. For instance, I spent many years looking at my keyboard while typing. I got kind of good at it. I didn&rsquo;t type fast, but my typing speed was kind of tolerable.</p> <p>It took a lot of time and effort to learn how to type properly, and my typing speed fell of a cliff for several months after I switched to touch typing. It took me a while just to reach my old speed, but once I did it, I was able to beat my old records with ease. Focusing my eves on screen also allowed me to notice mistakes faster, shortening the feedback loop. In order to learn something good, we often need to identify the things we&rsquo;re doing wrong and actively un-learn some bad stuff.</p> <p>For me, learning is pleasure, but I really, really don&rsquo;t like to un-learn things. It feels like a fight against myself, but it pays off in the long run. Fun fact, John Maynard Keynes had a few things to say about focusing too much on long-term thinking:</p> <blockquote> <p>In the long-term we are all dead</p> </blockquote> <p>That&rsquo;s also worth reflecting on.</p> vi https://bubelov.com/blog/2021/vi/ Sun, 27 Jun 2021 00:00:00 +0000 https://bubelov.com/blog/2021/vi/ <p>I use JetBrains IDEs for most of my programming tasks, and it works great on my PC. GUI text editors are nice, but I often need to connect somewhere via SSH and write some scripts or edit config files on a remote host.</p> <p>For a long time, nano was my tool of choice. It&rsquo;s easy to use, but it has certain limitations. Luckily for me, nano was available pretty much everywhere I went, except for my old OpenWRT router. OpenWRT doesn&rsquo;t have nano out of the box and supposed to use vi. This was the first time I really had to use it, and it wasn&rsquo;t a pleasant experience, to say the least.</p> <p>Time passed, and I started to get curious about vi and vim.</p> <p>I mentioned <a href="https://bubelov.com/blog/2021/unix-for-people/">UNIX for People</a> a couple of months ago, and it was a fun read. It encouraged me to finally try vi. It&rsquo;s tough at start, but, the longer I use it, the more exciting it gets.</p> <p>Shells have the same effect. There are many front-loaded things in life, they require serious commitment, and they take a long time to pay off. We&rsquo;re encouraged to take shortcuts and prioritize quick solutions over learning how to do things in the most productive way. Otherwise, we wouldn&rsquo;t have Stack Overflow.</p> Learning vs Productivity https://bubelov.com/blog/2021/learning-vs-productivity/ Fri, 25 Jun 2021 00:00:00 +0000 https://bubelov.com/blog/2021/learning-vs-productivity/ <p>Being productive is important, and it feels like something every responsible individual should strive for. Another thing which is essential for the most of us is learning new things. We don&rsquo;t want our doctors to be productive and enthusiastic at practicing obsolete medicine, we expect them to spend decades sharpening their skills and broadening their understanding of human body. Is it really different with computer science? Time files, market demands change, and we need to adjust to those changes. There is definitely a conflict between being educated and being productive, in a common sense of this word. Both activities require the same resource: time, and everyone has only 24 hours in a single day. I&rsquo;m not even talking about recreation, family time and so on. It&rsquo;s pretty hard to find the right balance, but avoiding extremes seems like a sensible way to go.</p> <p>Learning new things gets harder as we age, and I&rsquo;m not talking about biological factors. People tend to get used to certain lifestyles and, more often than not, borrow from future selves by taking different forms of debt. More learning often means less short-term productivity and lower short-term income. Being able to stay up to date and broaden your knowledge becomes a rarity. I don&rsquo;t know many people who pay enough attention to education after they get their degrees and join the workforce. Interestingly, many things seem to have changed recently, with millions of people switching to remote work and flexible hours. It looks like this productivity craze may come to an end soon. There is more in our lives than being 100% productive and being productive at wrong things is, well, counter-productive.</p> Setting Up SSH Passwordless Auth https://bubelov.com/blog/2021/ssh-passwordless/ Wed, 23 Jun 2021 00:00:00 +0000 https://bubelov.com/blog/2021/ssh-passwordless/ <p>There are many ways to access remote servers, but the most popular is <a href="https://en.wikipedia.org/wiki/Secure_Shell_Protocol">Secure Shell Protocol</a> (SSH). All popular operating systems ship with an SSH client and server packages installed by default, which means you can connect to other computers as well as to allow them to use your machine. That&rsquo;s cool, but also a bit concerning: how can we make sure only the right people can access our computers?</p> <p>There are many ways to authorize via SSH, but the most popular are password auth and key-based auth. Among those two methods, key-based auth is preferable because it&rsquo;s much harder to guess an average cryptographic key than the average password. You can use SSH to generate so-called public-private key pair. After that, you can copy your public key to a machine you want to access remotely and place it in <code>authorized_keys</code> directory. After that, you can go ahead and connect to that computer without typing any passwords. This method is both convenient and secure.</p> <p>Let&rsquo;s say we generated a key pair already, how do we copy our public key to a remote host? Looks like a chicken and egg problem. Many VPS providers solve this problem by asking for your public key before creating your first server, so they can add it to <code>authorized_keys</code> every time they bring up a fresh server for you. Another way to set up key-based auth is to start with password auth and then use a program called <code>ssh-copy-id</code>. It will attempt normal password auth and, once you type your password, it will add your public key to <code>authorized_keys</code> directory on the remote host. In other words, this program &ldquo;pushes&rdquo; your public key to a remote server. The downside? You have to enable less secure password auth first and then manually turn it off.</p> <p>I&rsquo;ve been happily using <code>ssh-copy-id</code> for years, but this mouth, I&rsquo;ve found out that it&rsquo;s also possible to &ldquo;pull&rdquo; my public key without setting up password auth. It&rsquo;s especially useful when I have physical access to a new machine, and I want to be able to connect to it remotely from my main computer at a latter stage. How is it possible to pull a public key from a machine which might be out of reach? Even if it&rsquo;s reachable, it won&rsquo;t give any files to an unrecognized client. We have a chicken and egg problem again. Well, pretty much everyone uses GitHub, and it will happily give you public keys of any user. Public keys aren&rsquo;t secret, so there is no reason to hide them anyway. The tool that can pull public keys of any GitHub user is called <code>ssh-import-id</code> and it&rsquo;s installed by default on most Linux distributions.</p> RaspiBolt https://bubelov.com/blog/2021/raspibolt/ Sun, 20 Jun 2021 00:00:00 +0000 https://bubelov.com/blog/2021/raspibolt/ <p>I&rsquo;m a big fan of Raspberry Pi, and I&rsquo;m also a big fan of Bitcoin. What if there were a guide on how to combine these amazing things? It turns out, there are a few such guides, and my favorite one is <a href="https://stadicus.github.io/RaspiBolt">RaspiBolt</a>. It can guide you through every step of setting up a fully functional Bitcoin node on a Raspberry Pi. It also has a section on enabling instant payments via Lightning Network. I enjoyed reading this guide and all of its extra tips and tricks.</p> Breez Wallet https://bubelov.com/blog/2021/breez/ Sat, 19 Jun 2021 00:00:00 +0000 https://bubelov.com/blog/2021/breez/ <p>Bitcoin is hard to use, and Lightning Network just adds an insult to injury. Convincing everyone to run their own self-sovereign nodes is a pipe dream. It would be nice, but it&rsquo;s impossible. It&rsquo;s like some kind of communist utopia, except for the fact that it&rsquo;s radically capitalist in its nature. I&rsquo;m in no way discourage setting up self-hosted nodes, but we also need to think about the people who won&rsquo;t do that, for various reasons. How do we connect them to Bitcoin infrastructure?</p> <p>There are a few mobile wallets which look promising. Most of them are overly centralized, which makes them just an incremental improvement over our legacy financial system. We can do better, and <a href="https://breez.technology/">Breez Wallet</a> is a good example. Breez allows people to accept LN payments instantly, it&rsquo;s open source, and it doesn&rsquo;t rely on a centralized backend in any significant way.</p> <p>Breez-like wallets are great for merchants who want to accept Bitcoin but don&rsquo;t know how. Needless to say, many merchants in developing countries don&rsquo;t even have enough extra money to buy a hardware for their own Bitcoin full node. Breez only needs a smartphone an Internet connection, which widens its audience. This wallet can also be used for sending payments, and the fee it charges is pretty low.</p> Learning Bitcoin From The Command Line https://bubelov.com/blog/2021/bitcoin-cli/ Tue, 15 Jun 2021 00:00:00 +0000 https://bubelov.com/blog/2021/bitcoin-cli/ <p>I like this <a href="https://github.com/BlockchainCommons/Learning-Bitcoin-from-the-Command-Line">guide</a>, but it&rsquo;s a bit outdated. It&rsquo;s still worth reading if you&rsquo;re curious how Bitcoin and Lightning Network work.</p> Bash Manual https://bubelov.com/blog/2021/bash-manual/ Sun, 13 Jun 2021 00:00:00 +0000 https://bubelov.com/blog/2021/bash-manual/ <p>I&rsquo;ve been using Bash for a long time, but I never read its manual. As it turned out, not reading the manual was clearly a mistake, which I was able to correct this month. Bash might look rather chaotic, but it becomes much easier to grasp if you read the <a href="https://www.gnu.org/software/bash/manual/">manual</a> instead of Stack Overflow.</p> El Salvador https://bubelov.com/blog/2021/el-salvador/ Thu, 10 Jun 2021 00:00:00 +0000 https://bubelov.com/blog/2021/el-salvador/ <p>Time flies. Who could imagine that Bitcoin will be an official currency of a sovereign state? Some may say it&rsquo;s just a PR move, and those cynical bastards are dead right, but still, I feel that this event is important, and it&rsquo;s indicative of rising Bitcoin adoption.</p> Chaincode Labs Podcast https://bubelov.com/blog/2021/chaincode-podcast/ Fri, 04 Jun 2021 00:00:00 +0000 https://bubelov.com/blog/2021/chaincode-podcast/ <p><a href="https://podcast.chaincode.com/">Chaincode Labs Podcast</a> can be of interest to anyone curious about Bitcoin inner workings. It also offers some insights on what to expect from Bitcoin in the near and not so near future.</p> Google Folds to Antitrust Pressure https://bubelov.com/blog/2021/google-antitrust/ Mon, 31 May 2021 00:00:00 +0000 https://bubelov.com/blog/2021/google-antitrust/ <p>Most tech companies hate open source software and Google is not an exception. Those same companies also want to look like they actually love open source in order to boost their public image. When it comes to mobile operating systems, the naive view might sound like &ldquo;bad, bad Apple with their shady iOS and good guys Google with open source Android&rdquo;. Yes, Android is open source and open source is better than closed source, but it doesn&rsquo;t mean that Google really welcomes open source software. It tolerates it, but it intentionally makes it hard for users to get open source software.</p> <p>Why would Google complicate your life if you chose to use open source software? Well, Google&rsquo;s main business is tracking us and making money off any private data it can harvest. Open source software lets us escape Google&rsquo;s surveillance empire. The thing is: Android isn&rsquo;t really open source. Most of Android devices on the market have nothing to do with open source software. You simply can&rsquo;t opt out of closed source malware such us Google Play Services and Google Play Store.</p> <p>I don&rsquo;t think we&rsquo;ll see Android phones without Google Play Services any time soon, but it looks like Google is starting to fear that US courts will expose its anti-competitive practices within Google Play Store. Android doesn&rsquo;t force you to use that store, it just tries to make your life miserable if you choose any another app store, F-Droid being one of those alternatives. How can it do that? Well, for starters, it doesn&rsquo;t allow any app store except the one made by Google to keep your apps up to date. It also floods you with dangerous-looking warnings, attempting to portrait more open and privacy-respecting app stores as a security threat. These patterns are dark and nasty, but Google got away with using them for a long time.</p> <p>Luckily for Android users, it&rsquo;s not only Apple who started to feel a lot of antitrust pressure, and Google now tries to remove its nasty tricks before it&rsquo;s forced to do that by law. Starting with Android 12, all alternative app stores will be much easier to use. They will be able to update apps automatically, same way as Google Play Store updates its apps. It looks like we&rsquo;re one tiny step closer to de-Googling Android, and now it&rsquo;s time to pressure Google to make both Play Store and Play Services removable.</p> Backups and Restic https://bubelov.com/blog/2021/restic/ Thu, 27 May 2021 00:00:00 +0000 https://bubelov.com/blog/2021/restic/ <p>Backups are crucial, even if you rely on cloud storage solutions. Many people mistakenly believe that Dropbox or iCloud can never lose user data. It&rsquo;s not true at all, those things happen all the time. If you have anything of value, you should back it up from time to time. Cloud is not enough, you should have a real physical copy, just in case something goes wrong.</p> <p>I&rsquo;ve tried many backup solutions and none of them worked well enough for my needs. Finally, I&rsquo;ve found a decent backup tool: <a href="https://restic.net/">Restic</a>. It&rsquo;s fast, secure, it&rsquo;s written in a decent language (Go), and it also supports cheap S3 cold storage.</p> <p>My backup routine looks like this:</p> <ol> <li>A dedicated backup host launches a backup job once a day.</li> <li>Backup job pulls new data form every server containing valuable data via <code>rsync</code>.</li> <li>Then it adds those new changes to a Restic repository.</li> <li>After that, it sends those changes offsite via S3.</li> </ol> <p>With such a setup, my data always exists in at least three places:</p> <ol> <li>On production servers.</li> <li>On backup host.</li> <li>Offsite.</li> </ol> <p>Restic also encrypts the data before sending it offsite so there are no privacy issues with sending my backups to a data center. Having offsite copy is important, because things like fires and natural disasters can easily destroy all your copies if you keep them in a single physical location.</p> Bitcoin Energy Consumption https://bubelov.com/blog/2021/bitcoin-energy-consumption/ Tue, 25 May 2021 00:00:00 +0000 https://bubelov.com/blog/2021/bitcoin-energy-consumption/ <p>To my surprise, people are still pissed about Bitcoin energy consumption. I mean, we&rsquo;re exchanging something which we value less for something which we value more every single day. If you buy a cup coffee for $5, it basically means that you value it more than the extra $5 on your bank account. Bitcoin energy consumption is only a problem if we lose more than we gain.</p> <p>What are our gains, exactly? The popular opinion goes: a bunch of crypto bros are trying to get-rich-quick at the expense of everyone else. To add an insult to injury, it looks like they are quite successful in that endeavor. The reality is: Bitcoin is our only chance to stay free in the modern world. As a society, we&rsquo;re on a road to a dystopian future where every financial transaction is monitored and any person risks being separated from their savings by corrupt or delusional politicians, or even a faulty algorithm.</p> <p>Cash is getting out of fashion but a cashless society without open and non-discriminating monetary system is a dead end. Needless to say, half of the world population lives in non-democratic countries and cashless systems make those people more vulnerable to political pressure. How much are we willing to spend in order to have a truly inclusive financial system? How important is it to have some king of digital gold which stays out of reach of central bankers and their printing presses?</p> <p>Bitcoin is a safe heaven. It protects people from abuse, and it limits the power of the elites, and those properties are extremely valuable.</p> curl Book https://bubelov.com/blog/2021/curl-book/ Mon, 17 May 2021 00:00:00 +0000 https://bubelov.com/blog/2021/curl-book/ <p>I&rsquo;ve been doing some server side tasks recently, and it&rsquo;s hard to overestimate the usefulness of a tool called <code>curl</code>. It&rsquo;s an open source program which enables billions of devices to transfer data across the web in a safe and reliable way. By the way, the author of this tool has a <a href="https://daniel.haxx.se/">nice blog</a>, and he also authored a book called <a href="https://everything.curl.dev/">Everything Curl</a>.</p> <p>This book is still work in progress, but it already helped me to discover a few hidden tricks of <code>curl</code>. This book is a must-read for anyone who does HTTP for a living.</p> How to Publish Things Online https://bubelov.com/blog/2021/how-to-publish-things-online/ Thu, 13 May 2021 00:00:00 +0000 https://bubelov.com/blog/2021/how-to-publish-things-online/ <p>This is an opinionated yet comprehensive guide on publishing your digital content online. The method I describe prioritizes simplicity, minimalism, vendor-independence and censorship resistance.</p> <p><figure> <a href="https://bubelov.com/blog/2021/how-to-publish-things-online/wide_hu_c91c09491b5247d6.webp"> <img src="https://bubelov.com/blog/2021/how-to-publish-things-online/wide_hu_9e4bff716a6a3335.webp" alt="" /> </a> <figcaption>Photo by <a href="https://unsplash.com/@brunus">Bruno Martins</a></figcaption> </figure></p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#isnt-publishing-a-solved-problem">Isn&rsquo;t Publishing a Solved Problem?</a></li> <li><a href="#go-static">Go Static</a></li> <li><a href="#websites-and-webservers">Websites and Webservers</a></li> <li><a href="#operating-system">Operating System</a></li> <li><a href="#hosting-provider">Hosting Provider</a></li> <li><a href="#connecting-to-your-server">Connecting to Your Server</a></li> <li><a href="#setting-up-webserver-software-apache2">Setting Up Webserver Software (Apache2)</a></li> <li><a href="#deploying-a-static-website">Deploying a Static Website</a></li> <li><a href="#setting-up-dns-records">Setting Up DNS Records</a></li> <li><a href="#getting-a-tls-certificate-from-letsencrypt">Getting a TLS Certificate From LetsEncrypt</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="isnt-publishing-a-solved-problem">Isn&rsquo;t Publishing a Solved Problem?</h2> <p>There are many ways to publish things online, so why reinvent the wheel? Most publishing platforms are easy to use, but they usually hide tremendous complexity under the hood. That hidden complexity can be far more dangerous than the visible one though. By choosing an inherently complex system managed by a third party you make yourself vulnerable to censorship and exploitation.</p> <h2 id="go-static">Go Static</h2> <p>Self-publishing can be easy and fun if you pick the right tools. Not every website is created the same, so it&rsquo;s important to keep things simple and focus on your content instead of your infrastructure. Static websites allow you to publish your content effortlessly. You can read more about static websites <a href="https://bubelov.com/blog/2022/static-websites/">here</a>.</p> <h2 id="websites-and-webservers">Websites and Webservers</h2> <p>Once your website is ready to be shared with the whole world, you need to figure out how to publish it. Most websites are striving to have easy memorable names so people can just type those names in their browsers when they want to see content.</p> <p>Web browsers act as <code>HTTP clients</code>, and they know polite ways of asking <code>HTTP servers</code> to serve all kinds of webpages for them. You can find a full description of an HTTP protocol in <a href="https://tools.ietf.org/html/rfc7230">RFC 7230</a>.</p> <p>Don&rsquo;t be afraid of wordy technical documents, HTTP is actually pretty simple and it can give you a lot of insights on what your browser is actually doing and how to fix the most common issues you might bump into while browsing the web.</p> <p>Client and server are popular abstractions used in many communication protocols, and HTTP is no exception. Protocols are like languages and it&rsquo;s crucial for both a client and a server to speak the same language. Their actual roles can be radically different though. By publishing a website, what we really want is to make it possible for various <code>HTTP clients</code> (browsers) to reach our <code>HTTP server</code> and display its content.</p> <p>Websites can&rsquo;t serve themselves, which means we need to install a special piece of software called a webserver and tell it to serve our websites to all interested clients. There are two most popular and mature open source webservers: <a href="https://httpd.apache.org/">Apache2</a> and <a href="https://nginx.org/en/">Nginx</a>. It&rsquo;s really hard to choose the winner here, because they both have a similar market share of about 35% and both of them are fast and well-documented. In this guide, I&rsquo;m going to use Apache2, because I use Nginx most of the time and playing with a new piece of software is fun, isn&rsquo;t it?</p> <h2 id="operating-system">Operating System</h2> <p>Every website needs a webserver and every webserver needs an operating system. The most popular operating system for running webservers is Linux. There are plenty of free and open source Linux distributions and all of them share the same kernel and they are more similar than they are different. It&rsquo;s hard to make a wrong choice here, but sticking with a popular distribution such as Ubuntu seems like a wise choice.</p> <p>Some people point out that Ubuntu isn&rsquo;t as free and non-binding as the other Linux distributions. I partly agree, but we shouldn&rsquo;t forget that Ubuntu is based on Debian and you can always move to Debian without having to learn a completely new set of tools. <a href="https://canonical.com/">Canonical</a> offers a good product for free and there are serious checks on its powers, so I wouldn&rsquo;t worry about that, for now.</p> <h2 id="hosting-provider">Hosting Provider</h2> <p>Websites need webservers and webservers are programs, which means that they usually need an operating system to run on, but what does an operating system need? Well, an operating system is essentially a piece of software designed to run and manage other programs. Obviously, no software can run without hardware, but the line here isn&rsquo;t that clear in the sense that software is actually a kind of hardware. It&rsquo;s not an abstract idea disconnected from a physical world. Software has a physical form, it exists on our storage devices such as hard drives and SSDs.</p> <p>Philosophical questions aside, we really need some hardware in order to run an operating system, which naturally leads us to a hosting provider. This part is pretty important, because some hosting providers don&rsquo;t give their customers direct access to their servers and sometimes, you can&rsquo;t even choose an operating system. Those providers try to place themselves in between your website and its visitors. Their marketing departments work hard to convince you not to bother with setting up your own private server. Their goal is to sell you a custom product which will make it very hard for you to switch to another provider. This is called &ldquo;managed&rdquo; hosting, and it may sound like a good idea at first, until you experience customer lock-in, terrible user interfaces, and unresponsive support. Managing your own server isn&rsquo;t that hard, and it will save you a lot of time and nerves in the long run.</p> <p>The choice of a hosting provider isn&rsquo;t that important, as long as it gives you full access to your server. I often use Digital Ocean, and their service is more or less tolerable. I also use Scaleway, and it&rsquo;s a good and cheap choice if you want to host your website in Europe. As a rule of thumb, your website should be as close to its visitors as possible. Light travels fast, but not as fast to make the distance unnoticeable when you open a website hosted halfway around the world.</p> <h2 id="connecting-to-your-server">Connecting to Your Server</h2> <p>Servers are a bit different from traditional desktop computers. They rarely have a graphical user interface, and the best way to manage them is by using a textual one. You can also use a textual interface to manage your Linux desktop by using a terminal app. Windows machines also have their own flavor of terminals, so you can also talk to your Linux servers from a Windows machine. Let&rsquo;s say your hosting provider gave you an Ubuntu server with an IP <code>100.101.102.103</code>. To connect to its terminal, you can issue the following command:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ ssh [email protected] </span></span></code></pre></div><p>This command assumes that you want to log in as a <code>root</code> user. If you don&rsquo;t, just use the username supplied by your hosting provider. If you&rsquo;re not comfortable with textual interfaces, take your time, explore the file system and some basic commands. Mastering the command line is a long and insightful journey, but you don&rsquo;t really need to be a command line guru to set up a webserver.</p> <p>You can always disconnect from your remote shell by typing <code>exit</code>.</p> <h2 id="setting-up-webserver-software-apache2">Setting Up Webserver Software (Apache2)</h2> <p>Debian-based systems keep an internal database of compatible software. You can think of it as a large spreadsheet which has a bunch of columns like <code>name</code>, <code>version</code> and so on. Folks who maintain this database tend to update it rather often, so you might end up in a situation when your copy of this &ldquo;spreadsheet&rdquo; is a bit outdated. Luckily for us, Debian-based systems have a special command called <code>apt</code> which can be invoked in order to update our local package registry:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># APT stands for Advanced Package Tool, and we tend </span> </span></span><span class="line"><span class="cl"><span class="c1"># to use terms &#34;program&#34; and &#34;package&#34; interchangeably</span> </span></span><span class="line"><span class="cl">$ apt update </span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">5 packages can be upgraded. Run &#39;apt list --upgradable&#39; to see them. </span></span></code></pre></div><p>Installing new software on Debian-based systems and Linux in general is easy, especially if it&rsquo;s included in the official repositories of your Linux distribution. Apache2 is popular, and that&rsquo;s all you need to do in order to install it:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ apt install apache2 </span></span></code></pre></div><p>Let&rsquo;s check if it works. Try opening the following URL (don&rsquo;t forget to use your server&rsquo;s IP):</p> <p>http://100.101.102.103/</p> <p>Apache2 listens for new HTTP connection attempts coming to your server, and it should show you a page with a few interesting tips and tricks. I highly recommend reading this page in full. This HTML page is located at <code>/var/www/html/index.html</code> Feel free to edit this page or replace its contents with something like this:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="cp">&lt;!DOCTYPE html&gt;</span> </span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">html</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">head</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">meta</span> <span class="na">charset</span><span class="o">=</span><span class="s">&#34;utf-8&#34;</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">title</span><span class="p">&gt;</span>My test page<span class="p">&lt;/</span><span class="nt">title</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;/</span><span class="nt">head</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">body</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span>Hello World!<span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;/</span><span class="nt">body</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">html</span><span class="p">&gt;</span> </span></span></code></pre></div><p>After refreshing your browser, you should see the changes you&rsquo;ve made.</p> <h2 id="deploying-a-static-website">Deploying a Static Website</h2> <p>So, it looks like everything located in <code>/var/www/html/</code> is immediately visible from a browser window. What&rsquo;s so special about this directory? First, don&rsquo;t worry about other directories, they are NOT accessible via a browser. You should think twice before putting anything into this directory, but that&rsquo;s exactly where you should put your static website in order to make it visible to the rest of the world.</p> <p>So, once you set up your server, publishing your writings is as simple as pasteing a bunch of files into a directory. Here is an example of how to copy a directory named &ldquo;website&rdquo; from your computer to a remote server:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ rsync <span class="se">\ </span></span></span><span class="line"><span class="cl"> --checksum <span class="se">\ </span></span></span><span class="line"><span class="cl"> --recursive <span class="se">\ </span></span></span><span class="line"><span class="cl"> --verbose <span class="se">\ </span></span></span><span class="line"><span class="cl"> &lt;path_to_your_website&gt;/ [email protected]:/var/www/html </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1"># The actual output will depend on your data. It usually shows</span> </span></span><span class="line"><span class="cl"><span class="c1"># which files are changed since the last sync. It copies only</span> </span></span><span class="line"><span class="cl"><span class="c1"># the changed files, which makes it super fast to deploy changes</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl">&gt; sending incremental file list </span></span><span class="line"><span class="cl">&gt; index.json </span></span><span class="line"><span class="cl">&gt; index.xml </span></span><span class="line"><span class="cl">&gt; blog/how-to-publish-things-online/index.html </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl">&gt; sent 74,448 bytes received 11,884 bytes 24,666.29 bytes/sec </span></span><span class="line"><span class="cl">&gt; total size is 163,314,189 speedup is 1,891.70 </span></span></code></pre></div><p>Where:</p> <ul> <li><code>&lt;path_to_your_website&gt;</code> is a path to the directory with your static website on your PC, something like <code>/home/john/website</code>.</li> <li><code>[email protected]</code> identifies your server. Don&rsquo;t forget the leading slash after the semicolon, it plays an important role.</li> <li><code>/var/www/html</code> is a path to a public web directory on your server.</li> </ul> <p>This method of deployment is extremely fast and convenient. All changes will be available to your audience in no time. It will also show you which files have changed since the last sync, which can help you to detect unexpected changes and figure out what&rsquo;s going on.</p> <h2 id="setting-up-dns-records">Setting Up DNS Records</h2> <p>URLs like <code>http://100.101.102.103/</code> are hard to memorize. It&rsquo;s one of the reasons why most websites use the Domain Name System (DNS). Memorizing names is easier than memorizing numbers, so you can think of DNS as of a big spreadsheet which matches different names with different IP addresses. Let&rsquo;s say you registered a domain name <code>writings.com</code>, now we need to connect it to our webserver. Our end goal is to make sure that when people type <code>writings.com</code> in their browsers, they will be shown your website. In the end, it&rsquo;s just a simpler way of typing <code>http://100.101.102.103/</code>, your readers will surely appreciate the convenience.</p> <p>To bind a domain name to your webserver&rsquo;s IP address, you have to own both of those things first. There are plenty of companies selling domain names, the only requirement you should have is the ability to manage DNS records. Most providers allow that. Owning a domain is not enough, you should also tell your domain where it should redirect all those browsers. This name-to-ip binding can be accomplished by simply adding the following DNS record:</p> <table> <thead> <tr> <th>Field</th> <th>Value</th> </tr> </thead> <tbody> <tr> <td>Record type</td> <td>A</td> </tr> <tr> <td>Name</td> <td>@</td> </tr> <tr> <td>Value</td> <td>100.101.102.103</td> </tr> <tr> <td>TTL</td> <td>Any sensible value. Choose 60 minutes if can&rsquo;t decide.</td> </tr> </tbody> </table> <p>You might need to wait for a bit for those changes to be applied. Try to ping your domain name:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># -c is short for packet count. Personally, I don&#39;t like </span> </span></span><span class="line"><span class="cl"><span class="c1"># short arguments due to their steeper learning curve, </span> </span></span><span class="line"><span class="cl"><span class="c1"># but I guess they come handy if you use this command </span> </span></span><span class="line"><span class="cl"><span class="c1"># hundreds of times</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl">$ ping -c <span class="m">4</span> writings.com </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl">&gt; PING writings.com <span class="o">(</span>100.101.102.103<span class="o">)</span> 56<span class="o">(</span>84<span class="o">)</span> bytes of data. </span></span><span class="line"><span class="cl">&gt; <span class="m">64</span> bytes from 100.101.102.103 <span class="o">(</span>100.101.102.103<span class="o">)</span>: <span class="nv">icmp_seq</span><span class="o">=</span><span class="m">1</span> <span class="nv">ttl</span><span class="o">=</span><span class="m">49</span> <span class="nv">time</span><span class="o">=</span>37.7 ms </span></span><span class="line"><span class="cl">&gt; <span class="m">64</span> bytes from 100.101.102.103 <span class="o">(</span>100.101.102.103<span class="o">)</span>: <span class="nv">icmp_seq</span><span class="o">=</span><span class="m">2</span> <span class="nv">ttl</span><span class="o">=</span><span class="m">49</span> <span class="nv">time</span><span class="o">=</span>39.2 ms </span></span><span class="line"><span class="cl">&gt; <span class="m">64</span> bytes from 100.101.102.103 <span class="o">(</span>100.101.102.103<span class="o">)</span>: <span class="nv">icmp_seq</span><span class="o">=</span><span class="m">3</span> <span class="nv">ttl</span><span class="o">=</span><span class="m">49</span> <span class="nv">time</span><span class="o">=</span>37.4 ms </span></span><span class="line"><span class="cl">&gt; <span class="m">64</span> bytes from 100.101.102.103 <span class="o">(</span>100.101.102.103<span class="o">)</span>: <span class="nv">icmp_seq</span><span class="o">=</span><span class="m">4</span> <span class="nv">ttl</span><span class="o">=</span><span class="m">49</span> <span class="nv">time</span><span class="o">=</span>39.0 ms </span></span><span class="line"><span class="cl">&gt; --- writings.com ping statistics --- </span></span><span class="line"><span class="cl">&gt; <span class="m">4</span> packets transmitted, <span class="m">4</span> received, 0% packet loss, <span class="nb">time</span> 3303ms </span></span><span class="line"><span class="cl">&gt; rtt min/avg/max/mdev <span class="o">=</span> 37.374/38.309/39.201/0.802 ms </span></span></code></pre></div><p>If it shows the IP address of your webserver, we&rsquo;re good to go. If not, don&rsquo;t worry and go make some coffee, it can take a while.</p> <h2 id="getting-a-tls-certificate-from-letsencrypt">Getting a TLS Certificate From LetsEncrypt</h2> <p>At this point, your website should be reachable by the following URLs:</p> <p>http://100.101.102.103/</p> <p><a href="http://writings.com/">http://writings.com/</a></p> <p>You might have noticed that your browser is not comfortable with those URLs. The thing is: they use insecure HTTP, and there are many good reasons to only use secure HTTP, or HTTPS. Insecure connections aren&rsquo;t private, and they enable ISPs and other actors to basically tap all of your communications over HTTP. That&rsquo;s why it&rsquo;s critically important to make your website available over HTTPS, and to do that, we need to obtain a thing called an HTTPS certificate.</p> <p>In ancient times, HTTPS certificates were expensive and hard to set up. Nowadays, thanks to the Snowden revelations and EFF&rsquo;s Let&rsquo;s Encrypt project, you can get certificates for free, and they usually work out of the box.</p> <p>First, I would recommend you to read about Let&rsquo;s Encrypt and Certbot, although it&rsquo;s not strictly necessary. In short, Certbot is an open-source program which can take care of setting up HTTPS certificates for you, free of charge. Now, let&rsquo;s install it:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ snap install --classic certbot </span></span></code></pre></div><p>After installing Certbot, just run it and follow the instructions:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ certbot </span></span></code></pre></div><p>That&rsquo;s it, now you have a website with a dedicated domain name. It also hides the traffic from anyone except your readers and yourself. HTTPS doesn&rsquo;t let anyone tap into your traffic and see what exactly your readers are interested in. Here is the final version of a website URL:</p> <p><a href="https://writings.com/">https://writings.com/</a></p> <h2 id="conclusion">Conclusion</h2> <p>Publishing your writings in a self-sovereign way is really easy, but it might feel frightening if you aren&rsquo;t familiar with the command line and server administration. The thing is: if writing is your work or even a hobby, being your own publisher is an investment worth having. The scheme I described allows you to update your website in no time, and it also delivers the best possible performance to your readers, saving them time and nerves.</p> <p>A sceptical person might argue that all this self-sovereignty is a lie, because you&rsquo;re still dependent on your hosting and DNS providers. Well, DNS is just a convenience feature, you can live without it. The real problem is your hosting provider, and the fact that it can block your webserver and take away your IP address. This is a seemingly unavoidable dependency and having this single dependency is still better than having many dependencies, isn&rsquo;t it?</p> <p>Is it even possible to make your website fully uncensorable? Yes, and it&rsquo;s actually pretty easy. The method I described needs only a few little adjustments in order to make your website available via a Tor network. Tor services can be easily hosted from home, and they don&rsquo;t even need IP addresses. That&rsquo;s what we&rsquo;re going to do next, stay tuned.</p> Money Explained https://bubelov.com/blog/2021/money-explained/ Wed, 12 May 2021 00:00:00 +0000 https://bubelov.com/blog/2021/money-explained/ <p>Most of the new movies on Netflix are garbage, but I really liked their new docu-series called <a href="https://www.netflix.com/title/81345769">Money Explained</a>. It borrows a few tricks from their previous series called <a href="https://www.netflix.com/title/80118100">Dirty Money</a>, and it&rsquo;s also focused on financial education mixed with stories about con artists and their schemes.</p> Darknet Diaries https://bubelov.com/blog/2021/darknet-diaries/ Mon, 10 May 2021 00:00:00 +0000 https://bubelov.com/blog/2021/darknet-diaries/ <p>I know a guy who&rsquo;s interested in information security, and he sent me the link to this podcast. I wish I knew about it earlier, it&rsquo;s really high quality stuff. Information security is a huge and mostly legal market now, with a bunch of different roles and modes of action. <a href="https://darknetdiaries.com/">Darknet Diaries</a> has a lot of interviews with a diverse set of people involved in different information security tasks. Each episode also features custom cover images drawn in a nice techno-dystopian theme.</p> Changelog Podcast https://bubelov.com/blog/2021/changelog-podcast/ Sun, 09 May 2021 00:00:00 +0000 https://bubelov.com/blog/2021/changelog-podcast/ <p><a href="https://changelog.com/podcast">Changelog</a> is an interesting podcast focused on programming in general. I wouldn&rsquo;t recommend every episode, but some episodes were pleasure to listen to.</p> Wasabikas https://bubelov.com/blog/2021/wasabikas/ Tue, 04 May 2021 00:00:00 +0000 https://bubelov.com/blog/2021/wasabikas/ <p>I discovered <a href="https://podcastindex.org/podcast/2415622">Wasabikas</a> while reading Blockstream&rsquo;s Mastodon feed. It has a conversation with Rusty Russel, Linux Kernel contributor turned Blockstream employee. Listening to his views was my goal, but it turned out, this podcast has a few more interesting guests.</p> New PC https://bubelov.com/blog/2021/new-pc/ Thu, 29 Apr 2021 00:00:00 +0000 https://bubelov.com/blog/2021/new-pc/ <p>I love assembling new PCs, and I finally convinced myself to buy another one. Here are the specs:</p> <table> <thead> <tr> <th>Component</th> <th>Model</th> <th>Comments</th> </tr> </thead> <tbody> <tr> <td>Case</td> <td>Fractal Meshify 2 Compact</td> <td>-</td> </tr> <tr> <td>Power supply</td> <td>Corsair CV 550</td> <td>-</td> </tr> <tr> <td>Motherboard</td> <td>X570 Aorus Master</td> <td>-</td> </tr> <tr> <td>CPU</td> <td>AMD Ryzen 7 5800X</td> <td>The warranty do not extend beyond the first purchaser. What a bunch of assholes.</td> </tr> <tr> <td>CPU cooler</td> <td>PC Cooler GI-CX 360 ARGB</td> <td>-</td> </tr> <tr> <td>RAM</td> <td>Corsair CMD 32G DDR4 3466 C16</td> <td>Old part, will replace later.</td> </tr> <tr> <td>GPU</td> <td>Nvidia GeForce GTX 1660 Ti</td> <td>Old part, don&rsquo;t buy that piece of garbage.</td> </tr> <tr> <td>SSD</td> <td>Samsung 980 Pro 1 TB</td> <td>-</td> </tr> </tbody> </table> <p>All of that hardware (except RAM and GPU), is latest-gen, which may raise a question about Linux compatibility. The answer is: it works like a charm! Linux kernel 5.11 fully supports the latest motherboard chipsets and AMD CPUs. Wi-Fi and Ethernet work without any issues. The motherboard has two Ethernet adapters from different vendors: one offers 2.5 gigabits, and the other one offers more conventional (and practical) 1 gigabit bandwidth.</p> <p>As you may notice, one of those components looks odd. Why pick Linux-hostile Nvidia GPU? Well, I didn&rsquo;t want to do it, but I wasn&rsquo;t able to buy a new GPU because of the global supply shortages. I have no idea what causes those shortages: some sources say it&rsquo;s a sign of inflation, others convinced that it&rsquo;s the result of shitcoin mining craze. Let me clarify: Bitcoin has nothing to do with GPUs, it uses different hardware for mining, but there are thousands of digital coins, mostly run by scammers, and they often require GPUs to operate.</p> Spring Boot https://bubelov.com/blog/2021/spring-boot/ Mon, 26 Apr 2021 00:00:00 +0000 https://bubelov.com/blog/2021/spring-boot/ <p>There is often a trade-off between doing something interesting and being productive. Recently, I&rsquo;ve been playing with Rust and I implemented a simple HTTP server based on <a href="https://tools.ietf.org/html/rfc7230">RFC 7230</a>. It was an interesting journey, but it took a lot of time in order to do a few basic things. After I finished with this server, one of my clients contacted me with an offer to write a simple JSON API. I really wanted to use my little Rust webserver, but I ended up using Spring Boot.</p> <p>Learning new things can be entertaining, and it can help us in the long run. That&rsquo;s good for me, but that would be horrible for my client. My productivity would drop, and it would be really hard to find another engineer to work on my Rust webserver. When it comes to business, it&rsquo;s all about making money, and it&rsquo;s really hard to make money if you&rsquo;re not productive at what you&rsquo;re doing. Backend software isn&rsquo;t my main focus, so I contacted a few friends and asked them for advice on which tools I should use in order to be productive. The consensus were on the side of Spring Boot.</p> <p>Spring Boot ended up to be a perfect choice. I must say, I&rsquo;m impressed how easy it is to set up and customize my Spring Boot project. The defaults are great, and its core foundations are solid. It&rsquo;s written in Java, but it also works great with Kotlin. Both of those languages are pretty strict, which means they prevent engineers from making costly mistakes. Ruby, JavaScript and Python frameworks are also popular, but they&rsquo;re based on shaky ground. They may also have mature tooling built around them, but nothing can compensate for the lack of strictness.</p> Security Vulnerabilities in curl https://bubelov.com/blog/2021/curl-security/ Sun, 25 Apr 2021 00:00:00 +0000 https://bubelov.com/blog/2021/curl-security/ <p>It&rsquo;s hard to find a software engineer who doesn&rsquo;t use curl. This little tool is written by Daniel Stenberg and this guy likes to blog about software and collect and visualize uncomfortable amounts of data. In his <a href="https://daniel.haxx.se/blog/2021/04/30/fixed-vulnerabilities-were-once-created/">recent post</a>, he analyzes the data on security vulnerabilities in curl, and the results are pretty interesting.</p> <p>What caught my attention is the average age of security vulnerabilities: it&rsquo;s more than seven years. This got me thinking about the popular argument which states that we shouldn&rsquo;t update software outright and instead wait for some time just to make sure no security issues will be found. Based on curl data, waiting doesn&rsquo;t make any sense. When we update our programs, we indeed risk getting new vulnerabilities. The thing is: no one will know about them for a long time, but timely applied security updates help us to make sure that our software are free of known vulnerabilities, which is far more important.</p> Wi-Fi 6 Router https://bubelov.com/blog/2021/wifi-6-router/ Sat, 17 Apr 2021 00:00:00 +0000 https://bubelov.com/blog/2021/wifi-6-router/ <p>First, let me say that Wi-Fi 6 is not a revolutionary change in wireless communications. Yes, it&rsquo;s a bit faster, and it has certain features aimed to make Wi-Fi more efficient in crowded places, but overall, I didn&rsquo;t notice any difference in my home setup. I just saw a banner on my Internet provider&rsquo;s website, and I decided to ask my provider about the possibility of upgrading the router. It uses GPON, so I can&rsquo;t really go shopping and pick any router I like, unfortunately. It turned out, they offer free router upgrades, so I decided to use this opportunity. The router hardware is faster, which allows it to keep latency low under load, and their new firmware finally got advanced enough to allow custom MAC to IP mappings. I really missed that feature. Those improvements have nothing to do with Wi-Fi 6, by the way.</p> Ubuntu 21.04 https://bubelov.com/blog/2021/ubuntu-21-04/ Wed, 14 Apr 2021 00:00:00 +0000 https://bubelov.com/blog/2021/ubuntu-21-04/ <p>I didn&rsquo;t plan to upgrade my Ubuntu 20.04 installations till the next LTS release, but purchasing new AMD CPU made me reconsider my commitment to LTS releases. The thing is: 21.04 has the newer kernel with complete support for new AMD CPUs. They work fine with 20.04, but they lack some thermal management features. I&rsquo;m not even sure if I need those features, but complete support indeed sounds better than partial support.</p> <p>21.04 isn&rsquo;t that different from 20.04. The most noticeable changes are related to user interface, and they are pretty minor and incremental. I&rsquo;ve been running this system for a while, and it seems to be very stable. The thing I miss the most from my old workstation is Wayland. I hope this GPU craze will end soon, and I&rsquo;ll be able to buy Wayland-friendly AMD GPU.</p> UNIX for People https://bubelov.com/blog/2021/unix-for-people/ Sat, 10 Apr 2021 00:00:00 +0000 https://bubelov.com/blog/2021/unix-for-people/ <p><a href="https://archive.org/details/unixforpeoplemod00birn">UNIX for People</a> was written in 1985, which makes it more interesting in some sense. It aimed to teach people how to use BSD UNIX, but now it can also tell us quite a lot about the history of computing.</p> The Elder Scrolls III: Morrowind https://bubelov.com/blog/2021/morrowind/ Fri, 02 Apr 2021 00:00:00 +0000 https://bubelov.com/blog/2021/morrowind/ <p>Recently, I&rsquo;ve come across an open source project called <a href="https://openmw.org/en/">openmw</a>. The goal of this project is to re-implement a game engine which runs Morrowind, fix some bugs and make that game playable on a current generation of operating systems, including Linux and macOS. Technically, it&rsquo;s still work in progress, but the game is already playable. I finished about half of the storyline and side quests, and I didn&rsquo;t find a single glitch. It&rsquo;s pretty shocking, given my low expectations of open-source games. This game is actually better and more stable than the original.</p> <p>Morrowind is old, it was released in 2002. Most games don&rsquo;t age well in a sense that their graphics and gameplay style tend to become obsolete in a few years after the initial release. Computer graphics improves all the time, interfaces and game mechanics evolve, and all of that affects our expectations, but when it comes to Morrowind, I can confirm that in 2021, it&rsquo;s as addictive as it was in 2002.</p> More Ansible https://bubelov.com/blog/2021/more-ansible/ Mon, 29 Mar 2021 00:00:00 +0000 https://bubelov.com/blog/2021/more-ansible/ <p>I continued moving my infrastructure to <a href="https://www.ansible.com/">Ansible</a> and the more I use it, the more I like it. It makes it easy to manage my servers, and it also forces me to look at Linux from a slightly different angle. This month, I moved my static websites to a separate host fully managed by Ansible and it was pretty straightforward. I also wanted to use <a href="https://nextcloud.com/">Nextcloud</a> without <a href="https://www.docker.com/">Docker</a>, and it seemed a bit overwhelming at first, but the whole process turned up to be quite simple. Ansible has a concept of roles, which are useful for repetitive tasks. For instance, I created a special Ansible role for setting up dynamic fan control on my <a href="https://www.docker.com/">Raspberry Pi</a> based servers.</p> Lightning Wallets and Full Nodes https://bubelov.com/blog/2021/lightning-and-full-nodes/ Thu, 25 Mar 2021 00:00:00 +0000 https://bubelov.com/blog/2021/lightning-and-full-nodes/ <p>Every Bitcoin user is supposed to have their own full copy of a blockchain. Unfortunately, it takes more than 400 GB of storage and this number goes up every 10 minutes. Most smartphones and even laptops don&rsquo;t have enough free space to store blockchain, which is a big problem. The first attempted solution is called SPV (<a href="https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki">BIP 37</a>), short for Simple Payment Verification. SPV-enabled Bitcoin wallets aren&rsquo;t required to store a full copy of blockchain, and they often need less than a few hundreds of megabytes.</p> <p>SPV is not an ideal protocol, and it has some serious flaws. Even if it was flawless, having fewer copies of blockchain weakens the whole Bitcoin network. You should consider running your own full node if you&rsquo;re in a position to do so. For other situations, it&rsquo;s nice to have a way of using Bitcoin without setting up a full node. Being able to install a Bitcoin wallet by a single tap on your phone screen certainly helps the adoption.</p> <p>I&rsquo;ve been playing with <a href="https://raspiblitz.org/">RaspiBlitz</a> recently, mostly because I wanted to evaluate the current state of Lightning Network. It&rsquo;s been a pleasure, so far, but it requires some technical know-how, and you also need a few hundred dollars worth of hardware in order to bring up a node. This doesn&rsquo;t look like something with an explosive potential for mass adoption, which led me to the following question: is it possible to have a Lightning wallet without having a full node? I&rsquo;d love to have such an app on my phone.</p> <p>It turned up, there are a few ways to run a Lightning wallet without having a full copy of Blockchain. The most interesting one is called Neutrino, or <a href="https://github.com/bitcoin/bips/blob/master/bip-0157.mediawiki">BIP 157</a> / <a href="https://github.com/bitcoin/bips/blob/master/bip-0157.mediawiki">BIP 158</a>. It can also be used by Bitcoin wallets, and it&rsquo;s supposed to replace SPV. This means that we can totally have a reasonably secure and private Lightning wallets in the form of simple mobile apps with no extra costs or manual setup. Looks like the future of Lightning Network is bright.</p> Apache2 https://bubelov.com/blog/2021/apache2/ Thu, 18 Mar 2021 00:00:00 +0000 https://bubelov.com/blog/2021/apache2/ <p>I expected Apache it to be ancient, slow, and hard to set up. None of that is actually true. Apache2 is an easy‑to‑use, transparent, and well‑documented web server. The documentation is a pleasure to read, and Ubuntu integration is excellent.</p> <p>My demands are modest and I don&rsquo;t need many extras, but decent TLS certificate management is a must these days and it&rsquo;s not built-in . The <code>certbot</code> package is just one <code>apt install</code> away though, and it even provides a Systemd timer that handles automatic renewals.</p> Studies in Pessimism https://bubelov.com/blog/2021/studies-in-pessimism/ Tue, 16 Mar 2021 00:00:00 +0000 https://bubelov.com/blog/2021/studies-in-pessimism/ <p>I first heard of Schopenhauer during my college years, and, although he seemed like an interesting philosopher, I&rsquo;ve never read any of his books. I don&rsquo;t really remember how I got the link to <a href="https://www.youtube.com/watch?v=-xPqPu4KT4o">this audiobook</a> a couple of weeks ago, but I&rsquo;m glad I&rsquo;ve got it. There is something deep in his thoughts that rings true to our nature. I get why some people see him as a king of pessimism, but don&rsquo;t let this playful mockery devalue the arguments Schopenhauer makes.</p> <p>I&rsquo;m casually aware of the idea of treating the will as a separate and almighty entity:</p> <blockquote> <p>A man can do as he will, but not will as he will.</p> </blockquote> <p>This particular book is a loose set of ramblings on different topics, most of them are pretty amusing. The part about women didn&rsquo;t age well, though.</p> Jabra Evolve 30 II https://bubelov.com/blog/2021/jabra-evolve-30/ Thu, 11 Mar 2021 00:00:00 +0000 https://bubelov.com/blog/2021/jabra-evolve-30/ <p>I&rsquo;ve been doing some podcasting in the last few months, and it motivated me to buy a mic. I don&rsquo;t know much about mics and I&rsquo;m not ready to waste a lot of money on a thing that I might not need, so I decided to start with a simple headset from Jabra and I&rsquo;m pleased with a headset I&rsquo;ve got. It greatly improved the quality of my voice recording and the headphones themselves are also great. They don&rsquo;t cause pain in my ears even if I use them for a few hours. Most of other headphones do, and I&rsquo;m not sure if I&rsquo;m alone here or it&rsquo;s a common thing.</p> Europa Universalis IV https://bubelov.com/blog/2021/europa-universalis-4/ Thu, 04 Mar 2021 00:00:00 +0000 https://bubelov.com/blog/2021/europa-universalis-4/ <p>This game is extremely addictive, and I had a misfortune to touch it again. What surprised me is the fact that many game elements were significantly reworked since I played it last time about a year ago. That&rsquo;s not what you would expect from a game published around 2014. The authors also introduced a subscription model and this thing is pure evil and a huge disappointment. I&rsquo;m sure we&rsquo;ll see some subscription-only content and other kinds of dark patterns pretty soon.</p> PinePhone https://bubelov.com/blog/2021/pinephone/ Thu, 25 Feb 2021 00:00:00 +0000 https://bubelov.com/blog/2021/pinephone/ <p>I&rsquo;ve become a proud PinePhone owner a few days ago. This is an open and privacy-respecting alternative to Apple-Google duopoly in the smartphone market. This phone is capable of running mainline Linux kernel, which makes it not that different from a typical Linux desktop.</p> <p>Most of the Linux distributions have certain assumptions about your screen size as well as the availability of a keyboard, mouse, trackpad and other input devices. Now that we have Linux smartphones, we still need to make those distributions aware of mobile screens and touch input methods. That&rsquo;s exactly why I didn&rsquo;t expect PinePhone to be usable as a daily driver.</p> <p>It turned out, a lot of the heavy lifting has been already done by a diverse group of mobile Linux enthusiasts. In fact, many popular Linux distributions work pretty well on PinePhone. Let&rsquo;s start with the basics: phones should be able to make and receive calls and they&rsquo;re also expected to handle SMS messages. PinePhone already does it pretty well, although it still lacks MMS support, and it&rsquo;s unknown if it&rsquo;s able to receive emergency broadcasts. I never used MMS, so I don&rsquo;t care about it. Being able to receive an emergency broadcast is something I expect from a daily driver phone, but we&rsquo;ll have to wait for the next few emergencies to find out if those broadcasts will arrive.</p> <p>Basic phone stuff is cool, but what about touchscreen support? There are two popular touch-enabled mobile shells available:</p> <ul> <li><a href="https://developer.puri.sm/Librem5/Software_Reference/Environments/Phosh.html">Phosh</a></li> <li><a href="https://www.plasma-mobile.org/">Plasma Mobile</a></li> </ul> <p>Phosh is based on GTK, a popular Linux widget toolkit. It does a good job at adapting desktop GNOME experience to smartphone screens and it also supports touch input. Plasma mobile is KDE-based, and I didn&rsquo;t try it yet. The screenshots look pretty good, though. These two projects did a lot of heavy lifting in order to bridge the gap between traditional Linux desktops and Linux smartphones such as PinePhone.</p> <p>Having basic phone functions and a touch interface is essential but there are a lot of other things that work a bit different on smartphones. Here are a few examples:</p> <ul> <li>Power consumption. There is a huge room for improvement.</li> <li>GPS. Figuring out device location and keeping it up to date isn&rsquo;t as easy as it may sound.</li> <li>Alarms. Yes, it&rsquo;s actually a surprisingly complex thing to implement, and it&rsquo;s not working yet. Desktop computers aren&rsquo;t used as alarm clocks that often, so it&rsquo;s a new use case that needs to be implemented. Don&rsquo;t rely on PinePhone if you don&rsquo;t want to miss your alarms!</li> </ul> <p>As you can see, PinePhone is still work in progress. It&rsquo;s usable, and it&rsquo;s pleasant to use, but you have to know its quirks. All of its hardware components work great, and you can easily replace them if something goes wrong. As usual, the software has to catch up with the hardware in order for PinePhone to be accessible to non-techies. I&rsquo;m pretty sure I will be able to recommend such a phone to users without Linux knowledge in about a year or so.</p> State of the Lightning Network https://bubelov.com/blog/2021/state-of-lightning/ Wed, 24 Feb 2021 00:00:00 +0000 https://bubelov.com/blog/2021/state-of-lightning/ <p>I continued my experiments with Lightning Network. My focus was on the things that can go wrong if you decide to operate a Lightning node. It turned out, a lot of things can go wrong ranging from the typical ops problems such as hardware failures to the exotic and counterintuitive ones, such as Lightning backups.</p> <p>Let&rsquo;s start with the counterintuitive issues, as they&rsquo;re more interesting. Every good sysadmin knows that having regular backups is essential to make sure your data is safe. Having a backup is always good, it protects you from losing your precious data. When it comes to managing Lightning nodes, having a full backup can be an extremely dangerous thing because it can lead to the loss of your funds. The thing is: Lightning Network prevents cheating by punishing people for presenting an outdated states of their channels.</p> <p>The reason is pretty simple, let&rsquo;s say you opened a channel with a local bar, and it started with $100 on your side and $0 on the side of the bar. At some time in the future, all the money ends up on the side of the bar, since you paid with Lightning Network. What makes Lightning Network so fast and cheap compared with Bitcoin is the fact that you can move money from one side of your channel to another without triggering a costly and slow Bitcoin transaction, but as some point, you&rsquo;ll want to close the channel and both sides will be able to reclaim their bitcoins. Both sides know the latest state of the channel and if any side tries to cheat and broadcast a previous state ($100 on your side, for example), the other side can prove that it has a newer state and get all the money as a reward for detecting cheating. Well, the problem is: backups tend to have older states, so they can be seen as cheating, that&rsquo;s why it isn&rsquo;t a good idea to use full backups.</p> <p>That&rsquo;s crazy, right? How can we back up a Lightning node? There is a mechanism called <a href="https://github.com/lightningnetwork/lnd/blob/master/docs/recovery.md">Static Channel Backup</a>. It&rsquo;s not ideal, but it should work. The problem with this kind of backup is that it won&rsquo;t recover your channels in case of a node failure. It can only recover your bitcoins by force-closing all of your channels.</p> <p>There are currently no reliable ways to back up and restore Lightning channels. The best thing an operator can do is to minimize the probability of a node failure. What can fail if you&rsquo;re running Raspiblitz on a Raspberry Pi 4?</p> <ul> <li>The board itself. Just buy more boards and you&rsquo;ll be fine.</li> <li>An SD card. Again, redundancy is you friend. Just keep a spare SD card and you&rsquo;ll be able to recover your node without using backups.</li> <li>An SSD/HDD. Failure of a main storage device would be a problem. Luckily for us, SSD/HDD failure rates are pretty low, and it&rsquo;s unlikely to happen. If you&rsquo;re paranoid, set up a RAID array with a comfortable level of redundancy. I think it&rsquo;s an overkill unless your node is handling a lot of money.</li> <li>Electricity shortage. Unexpected shutdowns can corrupt your data, and you have to avoid such situations. Buying a good UPS should solve that problem.</li> </ul> <p>As you can see, good old redundancy can solve most of your problems if you want to operate a highly available Lightning node. Backups are tricky because using wrong backups can lead to the loss of money and using proper backups leads to force-closing all of your channels. Losing your channels is unfortunate, but it&rsquo;s a &ldquo;worst case&rdquo; that shouldn&rsquo;t really happen. Even if you&rsquo;re unlucky, the only thing you would lose is blockchain transaction fees.</p> Hellfire Pass https://bubelov.com/blog/2021/hellfire-pass/ Tue, 23 Feb 2021 00:00:00 +0000 https://bubelov.com/blog/2021/hellfire-pass/ <p>I was surprised to learn that Thailand was involved in World War II, and that it initially sided with the Axis powers. Although its involvement was limited, Japan used prisoners of war to build several key logistics projects in Thailand.</p> <p><figure> <a href="https://bubelov.com/blog/2021/hellfire-pass/wide_hu_866c16d67017ea9.webp"> <img src="https://bubelov.com/blog/2021/hellfire-pass/wide_hu_804986be8a6f2d3d.webp" alt="" /> </a> <figcaption>Photo by <a href="https://commons.wikimedia.org/wiki/User:Diliff">Diliff</a></figcaption> </figure></p> <p>I think Russians tend to have a biased view of recent history. WW2 was an important propaganda tool for Soviet leadership, and it continues to be exploited by the current elites. The goal of this propaganda is to frame WW2 as a conflict between socialism and fascism. The entire narrative focuses on the war between the Soviet Union and Nazi Germany, giving all the credit to Soviet leadership while condemning western powers for their perceived cowardice and self‑serving deals with the Nazis.</p> <p>Do westerners have an unbiased view of WW2? Not at all, which is why it&rsquo;s important to separate facts from speculation.</p> <p>Of course, Americans should remember that many of the cruelest and most notorious war criminals were actually Japanese. For many, Asia is simply &ldquo;too far from home&rdquo;, which also makes people in the rest of the world less interested in Asian history. Germany&rsquo;s image is still tainted, but Japan is all about anime, high tech, and pacifism. That seems a bit unfair, doesn&rsquo;t it?</p> <p>Recently, I&rsquo;ve been driving trough Kanchanaburi province in Thailand. It isn&rsquo;t the most popular tourist destination, but it has a few beautiful spots such as <a href="https://en.wikipedia.org/wiki/Erawan_National_Park">Erawan National Park</a>. After visiting a few random places, I decided to find a local museum because I wanted to know more about the history of this province. It turns out, there aren&rsquo;t many museums in Kanchanaburi, but there was a single place which clearly standed out: <a href="https://www.dva.gov.au/recognition/commemorating-all-who-served/memorials/memorials-asia-pacific/thailand">Hellfire Pass Interpretive Centre and Memorial Walking Trail</a>.</p> <p>It&rsquo;s hard not to notice how clean, ordered, and well‑maintained this place is. It&rsquo;s all relative, of course, I wouldn&rsquo;t be surprised to see this level of service and care in a big European city, but it&rsquo;s very unusual for a place located in the middle of the jungle.</p> <p>Those were my first impressions, but I still didn&rsquo;t know what the function of this place was. It turned out to be standing evidence of Japanese war crimes. During WW2, Japan captured many allied soldiers and civilians and used them to build its infrastructure. One of the most ambitious Japanese projects was a railway between Burma and Thailand. This railway was built by prisoners, prisoners who were treated with a cruelty that can easily match German war atrocities.</p> <p>Here are some numbers. This railway was built by 60,000 prisoners of war, and at least 12,000 of them had died during the construction. Measuring things and keeping numbers is a good way to understand the magnitude of certain historical events. 12,000 is a big number, but it&rsquo;s not enough to understand what happened. These people could be victims of some terrible incident. Maybe fire, or a tsunami? Well, that&rsquo;s why we need places like this which can give us some context and translate a set of dry numbers into personal stories and experiences.</p> <p>It turns out, most of those deaths were preventable. Those people died from malnutrition, beatings and the lack of basic medical supplies. This interpretive centre has a lot of materials which help us to remember that human cruelty is a truly universal thing. It&rsquo;s kind of obvious, but it feels like our culture has certain biases in favor of non-western societies. There is something mystical about Asia, people often think that Buddhism must be better and more enlightened than our usual religions. It&rsquo;s acceptable to criticise Christianity and uncover its flaws. It&rsquo;s also acceptable (but probably dangerous) to criticise Islam, but Buddhism has a completely different image in western eyes.</p> <p>Popular tourist destinations tend to exacerbate the stereotypes we have about different countries and their history, but there are many things they don&rsquo;t really want to tell. That&rsquo;s why I don&rsquo;t use agencies and plan my trips by myself. Living where locals live, going where locals go and trying to grasp local history can be a good addition to the standard tourist routes and activities.</p> Android Smartphones Will Last Longer https://bubelov.com/blog/2021/android-lifespan/ Sat, 20 Feb 2021 00:00:00 +0000 https://bubelov.com/blog/2021/android-lifespan/ <p>Android smartphones are notoriously short-lived. Recently, I found my old Nexus 6 in an old storage box. The hardware still works perfectly, but this phone was abandoned by Google 18 months since its initial release. Samsung&rsquo;s phones are no better. In fact, there are no manufacturers who support their Android devices for more than a couple of years. Why is it so?</p> <p>Well, because they can, that&rsquo;s why. Consumers were busy updating their phones every year of two, why would a phone or a chipset manufacturer support them for a longer time? The smartphone market was hot, but it cooled down quite a bit recently and now consumers want their phones to last much longer.</p> <p>Most Android smartphones are powered by Qualcomm chips, and now Qualcomm <a href="https://www.qualcomm.com/news/releases/2020/12/16/qualcomm-and-google-announce-collaboration-extend-android-os-support-and">is promising</a> to provide all the necessary updates for at least four years. That&rsquo;s a good start, but it&rsquo;s still not enough. There needs to be more pressure from the consumers when it comes to device longevity and repairability.</p> Automatic Updates on Linux Severs https://bubelov.com/blog/2021/linux-auto-updates/ Sun, 14 Feb 2021 00:00:00 +0000 https://bubelov.com/blog/2021/linux-auto-updates/ <p>Outdated software is a security nightmare and there are good reasons to keep it up to date. That&rsquo;s kind of a general rule which I tend to apply everywhere. Lately, I was involved in a discussion about the risks and benefits of automatic security updates applied to servers running hot Bitcoin wallets.</p> <p>It turned out, a lot of people in Bitcoin industry are strongly against automatic security updates. Their main concern is the possibility of <a href="https://en.wikipedia.org/wiki/Supply_chain_attack">supply chain attacks</a>. By running certain software you put a lot of trust in its developers, and you should never run the software made by people you don&rsquo;t trust. The naive assumption would be: I already trust the developers, so I should enable automatic updates. If those developers feel that the update is necessary, it probably is.</p> <p>I used to think like this, but this approach has some flaws. When I install and run a certain program, I can&rsquo;t avoid trusting the developers as well as the distribution channel and all the things in between. Why shouldn&rsquo;t I trust the updates then? Well, developers can get hacked, as well as the distribution channels. Every update can be compromised, it&rsquo;s not a risk-free action after all. On the other hand, not having the latest security updates is also risky, although it really depends on how you use unpatched software.</p> <p>That said, how can we balance those risks and benefits? It&rsquo;s more of an art than science, I guess. Let&rsquo;s say a hacker manages to get access to a hundred of Bitcoin wallets via supply chain attack. He&rsquo;d be very happy, indeed, and he&rsquo;d likely to try to transfer all the money to his own wallet, triggering a lot of drama in the community. This kind of attack can only harm people who updated from poisoned sources. That&rsquo;s why it might be wise to not update your software too often, especially if it runs something critical such as hot Bitcoin wallet. It&rsquo;s a kind of compromise, you don&rsquo;t want to miss on years of security updates but having a healthy lag of a few weeks can actually be beneficial. Of course, some updates might be critical, but you&rsquo;ll likely hear about them from many sources and be able to perform a manual out-of-schedule intervention.</p> Bond Market Updates https://bubelov.com/blog/2021/bond-market-updates/ Wed, 10 Feb 2021 00:00:00 +0000 https://bubelov.com/blog/2021/bond-market-updates/ <p>It was an interesting month for a bond market. The interest rates on 10-year American Treasuries have risen from ~1.10% to ~1.45% during February. Bonds are low-risk assets, some people even call them <a href="https://thismatter.com/money/investments/capital-allocation.htm">risk-free assets</a>. When interest rates go up, the value of the bonds goes down. This can mean many things, such as:</p> <ul> <li>People are less interested in risk-free assets because they&rsquo;re optimistic about the future of the economy, so they want to put their money into more risky and profitable assets.</li> <li>People expect more inflation in the future and demand some compensation.</li> </ul> <p>Many factors can be at play at any given time. Let&rsquo;s not forget that the Fed has been quite pro-inflationary lately. That doesn&rsquo;t mean people aren&rsquo;t optimistic about the end of the Coronavirus pandemic, for example. They have a reason to be optimistic, after all.</p> <p>The main issue with rising interest rates is the fact that a lot of countries and corporations have a huge amount of debt. It&rsquo;s not a problem when the interest rates are near zero, but it&rsquo;s hard to find money to pay interest on your loans once the rates go up. The corporations may be forced to cut their spending, the governments may introduce austerity measures and all of those things slow down the rate of economic growth. Needless to say, it never fully recovered from the Great Recession, and the perspective of further slowdown might blow up our already unstable political systems.</p> RaspiBlitz https://bubelov.com/blog/2021/raspiblitz/ Sun, 31 Jan 2021 00:00:00 +0000 https://bubelov.com/blog/2021/raspiblitz/ <p>The oldest post in my blog is a book review. What is this book about? Bitcoin! Despite this fact, I&rsquo;m not an expert in Bitcoin. I love the ideas behind it, but I never read its source code nor that I follow any of the latest developments in this scene. Some of my friends tried Bitcoin mining. It didn&rsquo;t end well, as far as I know. Some entertained this idea but didn&rsquo;t act on it for various reasons.</p> <p>It always made me feel a bit guilty. I mean, if I&rsquo;m excited about Bitcoin, shouldn&rsquo;t I help this project in one way or another? Words are great, but is it enough to maintain truly decentralized and democratic money?</p> <p>Some people run their own Bitcoin nodes. I did it for a few months, but it&rsquo;s pretty expensive to do in the cloud. Blockchain takes a lot of storage space and this space is expensive to rent in the cloud. Using popular cloud services also kind of defeats the purpose of having more Bitcoin nodes. Having more nodes can make Bitcoin more secure and decentralized, but imagine a situation when all the bitcoin nodes are hosted on AWS. It takes a single decision of a single company to shut down the whole thing.</p> <p>These thoughts led me to a conclusion that I should try to run a Bitcoin node from home. It&rsquo;s easier said than done, because servers need maintenance, and they may also produce a lot of noise and heat. Those constraints prevented me from running my own node, until some person on Mastodon mentioned a project called <a href="https://github.com/rootzoll/raspiblitz">RaspiBlitz</a>.</p> <p>In short, RaspiBlitz is an easy to install Bitcoin and Lightning node that can be run on a Raspberry Pi 4. I decided to try it and I must say: it exceeded my expectations. It literally takes a few minutes to set it up and running, and it allowed me to experiment with many new and exciting projects in Bitcoin space.</p> <p>RaspiBlitz can also run behind Tor. I never used Tor, but now I have a reason to play with it. I think that people who are interested in Bitcoin should have a basic operational knowledge, just in case things go sour with the regulators. It will be our responsibility to support the network and make sure it can resist attacks from various bad actors, including rouge nation states.</p> Digital Markets Act https://bubelov.com/blog/2021/digital-markets-act/ Thu, 28 Jan 2021 00:00:00 +0000 https://bubelov.com/blog/2021/digital-markets-act/ <p>Most of the tech innovations come from the United States. No doubt, it offers unique environment for ambitious people to build influential tech companies. Nevertheless, I&rsquo;m more interested in consequences of having a market dominated by a few monopolies and in the reasons why we don&rsquo;t see more competition.</p> <p>It sure seems like a market failure, although it&rsquo;s important not to be too paternalistic. We shouldn&rsquo;t assume that consumers are dumb when they make seemingly irrational decisions. I think markets may fail when people don&rsquo;t understand what kind of deals they&rsquo;re getting into. Software industry is the worst in this sense. Who reads terms and conditions? Among people who do, how many of them have a clear understanding of what&rsquo;s going on?</p> <p>Software companies enjoy locking people in their ecosystems. You have much more leverage over locked-in customers and there are a lot of nasty things you can do to extract profit just because once your customers are glued to your products, it&rsquo;s almost impossible to break free. If you think deeply about the biggest tech companies, it won&rsquo;t take you a long time to figure out that all of them are using the same playbook.</p> <p><a href="https://protonmail.com/blog/digital-markets-act-explained/">Digital Markets Act</a>, or DMA, does a good job at identifying many of those dirty tricks that help monopolies to suppress the completion. Here is a couple of examples:</p> <blockquote> <p>Article 6(f) — Do make platforms interoperable with other service providers. Gatekeepers would have to make their platforms open to some key third-party service providers, like payment providers, digital identity providers, or ad-tech sellers, on the same terms as their own services.</p> </blockquote> <blockquote> <p>Article 6(h) — Do make data portable and continuously accessible in real time. Gatekeepers would have to give all users the ability to download their data and take it to a rival. They must also make both end and business user data continuously accessible in real time to their competitors.</p> </blockquote> <p>My prediction is: if DMA passes in its current form, it will boost the competition in the tech space, and many other countries will follow EU lead.</p> Argon ONE M2 Review https://bubelov.com/blog/2021/argon-one-m2/ Mon, 25 Jan 2021 00:00:00 +0000 https://bubelov.com/blog/2021/argon-one-m2/ <p>SD cards are pretty good for their price, but they aren&rsquo;t remotely as fast and reliable as solid state drives. Argon ONE M2 looked like the most aesthetically pleasing way to connect an SSD drive to my Raspberry Pi 4 so I decided to give it a go.</p> <p><figure> <a href="https://bubelov.com/blog/2021/argon-one-m2/wide_hu_dc99ccb56fffe715.webp"> <img src="https://bubelov.com/blog/2021/argon-one-m2/wide_hu_eaa7e5d94b533d9e.webp" alt="" /> </a> <figcaption>Photo by <a href="https://www.argon40.com">Argon 40</a></figcaption> </figure></p> <p>I like my original <a href="../../../2020/argon-one">Argon ONE</a> case for its aesthetics and smart fan control. Fans are noisy, but they enable Raspberry Pi to do some CPU-intensive work without <a href="https://en.wikipedia.org/wiki/Dynamic_frequency_scaling">throttling</a>. Argon ONE supplies a script which monitors CPU temperature and turns on an on-board fan only when it&rsquo;s necessary.</p> <p>Not every program is <a href="https://en.wikipedia.org/wiki/CPU-bound">CPU-bound</a>, which means speeding up your CPU won&rsquo;t always make your programs more responsive. There are lots of programs which are <a href="https://en.wikipedia.org/wiki/I/O_bound">I/O bound</a>, meaning the faster storage device you have, the faster your programs run.</p> <h2 id="original-argon-one-case-review">Original Argon ONE Case Review</h2> <p>This post assumes you&rsquo;re familiar with the original Argon ONE case. If not, you can <a href="https://bubelov.com/blog/2020/argon-one/">check out my review</a>.</p> <h2 id="whats-changed">What&rsquo;s Changed</h2> <p>Let&rsquo;s take a quick look at all the parts:</p> <p><figure> <a href="https://bubelov.com/blog/2021/argon-one-m2/all-parts_hu_7bac5e2231de8bac.webp"> <img src="https://bubelov.com/blog/2021/argon-one-m2/all-parts_hu_774b6923ec266e7b.webp" alt="" /> </a> </figure></p> <p>The first thing which caught my attention was the extension board. It looks different:</p> <p><figure> <a href="https://bubelov.com/blog/2021/argon-one-m2/extension-board_hu_6a1a4a436db3dc06.webp"> <img src="https://bubelov.com/blog/2021/argon-one-m2/extension-board_hu_d52f87217622a279.webp" alt="" /> </a> </figure></p> <p>It looks like Argon ONE M2 has some properly sized HDMI ports. The original Argon ONE case uses weird micro HDMI ports and it&rsquo;s hard to find cables for them. This is a really nice improvement. Chances are, you already have an HDMI cable in your house. They usually come with TVs, game consoles, PC displays and other devices used to display stuff. With a normal HDMI port, you can simply &ldquo;borrow&rdquo; an HDMI cable from any of your other devices and you&rsquo;re good to go.</p> <p>Now, let&rsquo;s look at the top panel:</p> <p><figure> <a href="https://bubelov.com/blog/2021/argon-one-m2/fan-board_hu_c22244101e3cfb21.webp"> <img src="https://bubelov.com/blog/2021/argon-one-m2/fan-board_hu_e2dee800c85fe952.webp" alt="" /> </a> </figure></p> <p>Nothing interesting here, except the newly added IR port. The manual says it&rsquo;s supposed to be used with a proprietary remote. One more thing: don&rsquo;t forget to move the jumper to <code>ALWAYS ON</code> mode if you want your Raspberry Pi to recover automatically in case of a power outage.</p> <p>When I saw this switch for the first time, I thought it&rsquo;s responsible for controlling the fan, but the new manual says it&rsquo;s all about power management. The default mode is good for devices you actively engage with, such as a desktop computer or a game console. The <code>ALWAYS ON</code> mode is perfect for servers because they&rsquo;re supposed to self-heal and keep on doing what they&rsquo;re doing without your help.</p> <p>Now, let&rsquo;s take a look at the bottom panel with an M2 slot:</p> <p><figure> <a href="https://bubelov.com/blog/2021/argon-one-m2/m2-case_hu_74b0821e31320c8b.webp"> <img src="https://bubelov.com/blog/2021/argon-one-m2/m2-case_hu_299bb46256b83b44.webp" alt="" /> </a> </figure></p> <p>It looks like all M2 lengths are supported. The most important one is obviously 2280, so let&rsquo;s try to install an 80 mm long SSD module:</p> <p><figure> <a href="https://bubelov.com/blog/2021/argon-one-m2/m2-case-with-ssd_hu_45fceb182989fd3b.webp"> <img src="https://bubelov.com/blog/2021/argon-one-m2/m2-case-with-ssd_hu_1b803946792167c2.webp" alt="" /> </a> </figure></p> <p>It fits well enough, although the fact that it bends gives me chills. I don&rsquo;t think it will break it, so I guess that&rsquo;s fine. Keep in mind that this case supports SATA M2 SSDs only. Most SSDs on the market are actually NVMe, and they won&rsquo;t work with this case! I wish it supported NVMe SSDs, and I&rsquo;d say it&rsquo;s the biggest flaw of this product.</p> <p>Redirecting all the ports to the back of the case is a genius move. It makes connecting cables much easier which also makes the end result more aesthetically pleasing:</p> <p><figure> <a href="https://bubelov.com/blog/2021/argon-one-m2/pi-with-extension-board_hu_1b5b795d46bb93fb.webp"> <img src="https://bubelov.com/blog/2021/argon-one-m2/pi-with-extension-board_hu_80fa2244a72809a8.webp" alt="" /> </a> </figure></p> <p>Lastly, let&rsquo;s take a look at this strange little USB thing:</p> <p><figure> <a href="https://bubelov.com/blog/2021/argon-one-m2/usb-male-male_hu_d85ea806d03d9ba6.webp"> <img src="https://bubelov.com/blog/2021/argon-one-m2/usb-male-male_hu_bbb7ac0117a315d.webp" alt="" /> </a> </figure></p> <p>That&rsquo;s where the M2 magic happens. It connects the M2 module with one of the USB 3 ports on Raspberry Pi. USB 3 can handle high transfer speeds, especially if the case supports <a href="https://en.wikipedia.org/wiki/USB_Attached_SCSI">UASP</a>. Spoiler alert: it does!</p> <h2 id="conclusion">Conclusion</h2> <p>Argon ONE M2 is a good choice when you need high speed data I/O. It&rsquo;s also well-built and aesthetically pleasing, which is an important factor, in my opinion. The main drawbacks are high price and the lack of NVMe support. We often overestimate how much performance we would really need to run our software. I&rsquo;m using a $95 Android phone and It&rsquo;s pretty damn fast! If your workloads are I/O bound, this case can help you a lot. Otherwise, it might be better to stick with cheaper and slimmer options.</p> Ansible https://bubelov.com/blog/2021/ansible/ Sun, 24 Jan 2021 00:00:00 +0000 https://bubelov.com/blog/2021/ansible/ <p>I&rsquo;ve been managing a handful of servers lately, and the whole process quickly reached the point when it&rsquo;s very hard to figure out all the steps I need to do to reproduce a particular server in case of a failure (or even a simple migration). Docker makes simple deployments easy to migrate, but you still need to install Docker, Docker Compose, set up appropriate firewall rules, set up WireGuard tunnels, configure automatic updates, add monitoring agents, etc.</p> <p>Setting up a server isn&rsquo;t as easy and fast as it may sound. Ansible is a great open source tool that allows me to manage my fleet with ease. It also helps me to make sure that I didn&rsquo;t miss any important steps. It works over SSH, and I believe people can benefit from using it even if they have to manage a single server.</p> <p>Ansible is one of the tools which enable to define <a href="https://en.wikipedia.org/wiki/Infrastructure_as_code">infrastructure as code</a>. There are a few other tools in the market, but Ansible seem to work pretty well for my use cases, so I guess I&rsquo;ll stick with it for now.</p> Less Docker? https://bubelov.com/blog/2021/less-docker/ Fri, 22 Jan 2021 00:00:00 +0000 https://bubelov.com/blog/2021/less-docker/ <p>As I mentioned above, I&rsquo;ve been playing with Ansible lately. Ansible isn&rsquo;t directly comparable with Docker, but they do intersect in some ways. In both cases, you need to provide a set of actions, and the end result is having an easily reproducible system.</p> <p>I have a Nextcloud server, and it uses Docker Compose. It works pretty well. It fetches the latest version of Nextcloud container from Docker Hub with zero customizations, and it just works. All the complexity of Nextcloud configuration is hidden inside that container. That&rsquo;s helpful when you&rsquo;re an uncommitted user who just wants to try something. Sooner or later you might want to have a more fine-grained control over the configuration of your Nextcloud server. That&rsquo;s one of the use cases where Ansible shines.</p> Pinephone + Mobian https://bubelov.com/blog/2021/pinephone-mobian/ Tue, 19 Jan 2021 00:00:00 +0000 https://bubelov.com/blog/2021/pinephone-mobian/ <p>I&rsquo;ve been following Pinephone development for a while now. The hardware has some problems, the software is still incomplete, but it starts to look functional enough to start tinkering with this phone. I&rsquo;m a big fan of Debian-based systems and I decided to order Pinephone as soon as its makers announced their Debian Mobile package. It should arrive by the end of February. Can&rsquo;t wait to see what mobile Linux looks like.</p> Mastodon https://bubelov.com/blog/2021/mastodon/ Sat, 16 Jan 2021 00:00:00 +0000 https://bubelov.com/blog/2021/mastodon/ <p>I was a heavy Twitter user once, but those times are long gone. Not that I don&rsquo;t enjoy Twitter anymore, I just find their business model to be pretty nasty. I had a few Twitter pages in my bookmarks, mostly some people from Bitcoin community. Apparently, this community decided to migrate to <a href="https://joinmastodon.org/">Mastodon</a>, and it was such a relief for me. Mastodon allows me to subscribe to certain blogs via RSS. Twitter used to be like this, until they decided that sharing content doesn&rsquo;t suit their financial interests.</p> <p>I even created my own Mastodon blog, and it turned out to be pretty helpful. Some folks from the Bitcoin community helped me to set up a Bitcoin and Lightning node. Mastodon feels like microblogging done right. Highly recommend.</p> BeagleV https://bubelov.com/blog/2021/beagle-5/ Thu, 14 Jan 2021 00:00:00 +0000 https://bubelov.com/blog/2021/beagle-5/ <p><a href="https://beaglev.seeed.cc/">BeagleV</a> board looks promising. I like my Raspberry Pi 4 computers, but they have at least two severe problems:</p> <ul> <li>They require closed-source binaries to boot!</li> <li>They&rsquo;re ARM-based.</li> </ul> <p>RISC-V is a more open instruction set architecture. Is it that important? Yes, very. ARM licenses are pretty expensive, and it was used as a tool for political pressure. Business have no reason to trust or to pay ARM, RISC-V looks like a cheaper and safer bet.</p> <p>Closed binaries is an ugly compromise which Raspberry Pi folks had to accept. I don&rsquo;t think they&rsquo;re happy about it but I guess there are no other options. Well, it seems like the situation is changing.</p> UK Against Privacy in Browsers https://bubelov.com/blog/2021/uk-private-browsers/ Tue, 12 Jan 2021 00:00:00 +0000 https://bubelov.com/blog/2021/uk-private-browsers/ <p>I always found cookies unnecessary and very invasive, it&rsquo;s funny how UK government <a href="https://www.bbc.com/news/technology-55219750">tries to defend</a> this shady practice.</p> YouTube Alternatives https://bubelov.com/blog/2021/youtube-alternatives/ Sat, 09 Jan 2021 00:00:00 +0000 https://bubelov.com/blog/2021/youtube-alternatives/ <p>Most of the videos we watch are hosted by YouTube. They&rsquo;re marketed as free, but we pay for them with our attention (watching ads) and by putting ourselves under Google&rsquo;s surveillance. There are a few ways to have a cake and eat it, though. One of those options is <a href="https://newpipe.net">NewPipe</a>.</p> <p>This clever app allows us to watch YouTube videos without ads. It solves some problems with YouTube, but it&rsquo;s not a complete solution. We need to stop relying on YouTube to host our videos. There are a few promising projects and here is the two I find the most interesting:</p> <ul> <li> <p><a href="https://joinpeertube.org">https://joinpeertube.org</a></p> </li> <li> <p><a href="https://odysee.com">https://odysee.com</a></p> </li> </ul> <p>I started to follow a few channels on Odysee, and it works pretty well. I hope more authors start to cross-post their content to these services. They also experiment with the new ways of monetising content, which is nice.</p> Blockstream Jade https://bubelov.com/blog/2021/blockstream-jade/ Tue, 05 Jan 2021 00:00:00 +0000 https://bubelov.com/blog/2021/blockstream-jade/ <p>Blockstream is one of the most reputable companies in Bictoin space. They now offer <a href="https://blockstream.com/jade/">their own</a> hardware wallet. I wonder in what ways it will be different from Trezor. It seems to rely on QR codes, and it also has a built-in camera. That&rsquo;s something new.</p> Anita Posch Show https://bubelov.com/blog/2021/anita-posch-show/ Sun, 03 Jan 2021 00:00:00 +0000 https://bubelov.com/blog/2021/anita-posch-show/ <p>One of my favorite ways to catch up with some topic is listening to the latest podcasts. I&rsquo;ve been looking for a good Bitcoin podcast, and I found this <a href="https://bitcoinundco.com/en/">great set of interviews</a> made by Anita Posch. I know many of her guests, and it&rsquo;s been a pleasure to discover new Bitcoin personalities and listen to their ideas.</p> What a rough year! https://bubelov.com/blog/2021/what-a-rough-year/ Sat, 02 Jan 2021 00:00:00 +0000 https://bubelov.com/blog/2021/what-a-rough-year/ <p>I managed to avoid getting infected with COVID-19, but I had my share of medical issues during 2020. They weren&rsquo;t dangerous, but some were annoying and stressful as hell. Having a good health insurance is extremely important, especially if you live in a foreign country. I had to stay in hospital for one night after being bitten by a dog, and the bill was about $4,500. A decent insurance plan costs about $1,000 a year in Thailand, and it&rsquo;s very much worth it.</p> <p>My travel plans were ruined by COVID-19, and my only option was domestic tourism. I spent a few weeks on a road trip through Thailand, and it was a very pleasant experience.</p> <p>I continued moving to open source software, and it works pretty well for me so far. The future of open source software looks bright, and I&rsquo;m more excited about it than ever. I also published my own open source app which I&rsquo;ll keep improving during the next year. It&rsquo;s both a feed reader and a podcast player, and it has become my main source of news.</p> <p>I had plenty of free time this year, and it allowed me to think more deeply about many things, including work. I got more picky when it comes to taking new projects. I used to assume that pretty much any job is productive and valuable for society, so I was never too picky as long as there were enough money on the table. Looking back, I think it was a mistake. Some jobs are good, some jobs are evil. Evil people and evil companies never admit their nature, so it&rsquo;s important to do a due diligence unless you want to contribute to something that harms the world instead of improving it.</p> <p>I don&rsquo;t have any exact plans or goals for the next year. 2020 was full of uncertainty and who knows what&rsquo;s coming next. I guess I&rsquo;ll just keep doing what I&rsquo;m doing and see what happens.</p> <blockquote> <p>Do your duty, come what may.</p> </blockquote> Open Source and Smartphones https://bubelov.com/blog/2020/open-source-and-smartphones/ Sun, 27 Dec 2020 00:00:00 +0000 https://bubelov.com/blog/2020/open-source-and-smartphones/ <p>I use Android and I work on Android apps because it&rsquo;s the most open mainstream platform on the market. That doesn&rsquo;t mean I like it, it just means I dislike it less than the other options.</p> <p>What I would really like is to be able to run a fully open source OS like Linux on my smartphone. Android uses modified Linux kernel, but it&rsquo;s not a real Linux. I deeply respect people who try to make Android more open like the folks from LineageOS, but it seems like a pointless battle against a hostile platform. Google doesn&rsquo;t want Android to be fully open, and it controls this platform so there isn&rsquo;t much we can do about it.</p> <p>That said, Android is still the only game in town, but times are changing. I&rsquo;m pleasantly surprised how fast Pinephone moved from the idea to being a functional device selling worldwide. Using a Linux phone as a daily driver is closer to reality than ever, and I believe it could happen by the end of 2021.</p> Stop the Abusers: A Case for Punishing Anti-Competitive Tech Giants https://bubelov.com/blog/2020/regulation/ Sat, 26 Dec 2020 00:00:00 +0000 https://bubelov.com/blog/2020/regulation/ <p>I usually have a libertarian stance on most of the questions but regulation isn&rsquo;t one of them. I believe that markets don&rsquo;t really need interventions in the long run but certain interventions may speed up our progress and prevent some serious issues. In other words, although government interventions do not drive the progress, they can speed it up. That&rsquo;s why I&rsquo;m very pleased with the recent anti-thrust activity of the US regulators. There is no doubt that big tech companies are abusing their power and hurt the consumers. They must be stopped. We need to punish them for anticompetitive behaviour, and we need stronger privacy protections.</p> <p>The only purpose of business is to sell us stuff that makes our lives better. Competition makes sure that consumers get good stuff at reasonable prices, and the entrepreneurs get their well-deserved rewards. Without competition, there is no reason for entrepreneurs to create good stuff and there is no limit on the rewards, that&rsquo;s why we should not allow companies to destroy competition.</p> F-Droid https://bubelov.com/blog/2020/fdroid/ Fri, 25 Dec 2020 00:00:00 +0000 https://bubelov.com/blog/2020/fdroid/ <p>Most people think that every mobile phone has a single app store. iPhones have App Store and Android phones have Google Play, right? This isn&rsquo;t entirely true for Android, though. Yes, Google Play has a huge market share, and it&rsquo;s practically a monopoly, for various reasons, but no one really stops you from using alternative app stores.</p> <p>F-Droid is one of those alternatives. In fact, app store isn&rsquo;t the best name because all the apps listed there are free and open source. In Linux terms, it&rsquo;s a software repository. It&rsquo;s still pretty niche, but there are a few killer apps which make it very attractive.</p> <p>One of those apps is NewPipe. NewPipe is YouTube done right. It uses YouTube database, but it cuts ads, and it also allows you to play videos in the background. Some people might say that it&rsquo;s unethical to hack YouTube but doing unethical things to unethical companies is not a big deal in my book.</p> Thailand Road Trip https://bubelov.com/blog/2020/thai-road-trip/ Thu, 24 Dec 2020 00:00:00 +0000 https://bubelov.com/blog/2020/thai-road-trip/ <p>Thailand attracts lots of foreigners every year and that&rsquo;s why people tend to overestimate its reliance on the flow of international tourists. COVID-19 travel ban certainly hurts some businesses but not as hard as a casual observer may assume. Thailand generates about 10% of its GDP from tourism and about 40% of this sector actually comes from domestic tourism and not from foreign arrivals.</p> <p>I&rsquo;m not as obsessed with travel as some of my friends, but I do enjoy long car trips. Some people enjoy driving, but it&rsquo;s actually the only thing I don&rsquo;t like about travelling by car. The real benefit of using a car is the ability to explore places which are far from the major transport hubs and tourist hotspots. It gives me an opportunity to see things from a different angle. Such an experience tend to be more real and unpolished, it&rsquo;s hard to get it when you travel by plane or public transport.</p> <p>Thai roads are reasonably nice. The country itself isn&rsquo;t too big, and the population density is high, which makes it impossible to get lost even if you&rsquo;re completely unprepared for a long trip. There are plenty of gas stations everywhere, and they have pretty much anything that might be necessary to travel with comfort.</p> Vanilla CSS https://bubelov.com/blog/2020/vanilla-css-2/ Thu, 24 Dec 2020 00:00:00 +0000 https://bubelov.com/blog/2020/vanilla-css-2/ <p>Many modern websites are bloated and slow for no good reason. One of the trends of this year is our collective admission that complexity and bloat are real issues. The stuff that we make is getting more and more complex, and the more complicated it gets, the less we understand about its inner workings and the ways to make it work better and faster. That&rsquo;s the main reason I migrated this website to vanilla CSS. I don&rsquo;t want to contribute to ever-increasing complexity, and I believe that we need to focus more on simplifying our tools and approaches.</p> Markets, Mania, and the Search for Real Value https://bubelov.com/blog/2020/economics/ Tue, 22 Dec 2020 00:00:00 +0000 https://bubelov.com/blog/2020/economics/ <p>It&rsquo;s a common belief that markets can detach from underlying economies, so the price of financial assets may not reflect their fundamental valuations. We saw a huge drop in stock valuations during this year, but it quickly reversed and 2020 turned out to be one of the best years for stocks.</p> <p>How can a rational market be so volatile and uncertain? Let&rsquo;s take Bitcoin as an example. It dropped to $3,000 in Spring but it rose to $28,000 in December. What changed? Those are hard questions, and no one knows the answers.</p> <p>There are voices that attribute the rise of Bitcoin price to hedging against inflation and stock price declines, but I don&rsquo;t see any evidence for that. On the contrary, Bitcoin price collapsed exactly when the stock price did, so it didn&rsquo;t protect investors from the decline in stock valuations. Can it protect people from inflation? In theory, it might. In practice, we have no idea.</p> <p>In my opinion, both stocks and bonds are severely overvalued, and it might prop up the price of gold, Bitcoin and any other assets which are out of grasp of central banks during 2021.</p> Webfeeds https://bubelov.com/blog/2020/web-feeds/ Mon, 21 Dec 2020 00:00:00 +0000 https://bubelov.com/blog/2020/web-feeds/ <p>I stopped using social networks a couple of years ago, and I never looked back. Social networks are full of bullshit and self-promotion and this kind of content doesn&rsquo;t really resonate with me. I get it, some people want to know what&rsquo;s up with their closest friends, but the thing is, most of the people we get constant updates from via the social media are not our close friends. Using a social network feels like being a guinea big in some kind of social experiment in human deception.</p> <p>I get most of the news from RSS and Atom feeds. It&rsquo;s so much better this way. The closest thing to this experience is Reddit. I was addicted to Reddit for quite a while, and it felt much better than other mainstream social media, probably because it was built around certain topics and not people, so it was much easier to find something interesting.</p> The Pragmatist's Path to Free Software https://bubelov.com/blog/2020/open-source-2/ Sun, 20 Dec 2020 00:00:00 +0000 https://bubelov.com/blog/2020/open-source-2/ <p>My first operating system was Windows, and I loved it. Not that it was good, there were simply no alternatives. What else could a kid use? Using Linux requires pretty deep technical knowledge and using macOS requires a lot of money. It was true a couple of decades ago, and it&rsquo;s still true nowadays.</p> <p>Later in my life, I&rsquo;ve got some technical skills and curiosity to try Linux. My first attempts to use Linux failed miserably, but I was able to use Ubuntu a few years later when I got my first programming job. This job also improved my finances, so I was able to afford a MacBook and try macOS. It worked really well for me, mostly because the only things I needed to run were a Java IDE and a Chrome browser. That&rsquo;s a pretty good deal, really. You get everything you need, wrapped in a polished user interface.</p> <p>It&rsquo;s clear that I was motivated by pragmatic reasons. I didn&rsquo;t really care about open source software or replaceable hardware until my new MacBook started to fall apart. It may sound like an impulsive rage-quit, but at some point I just gave up on MacBooks and ordered Dell XPS 13 which is known to play nice with Linux.</p> <p>Switching to Linux wasn&rsquo;t easy, but it&rsquo;s one of those things which are unpleasant at the beginning but hugely rewarding in the long run. When it comes to graphical user interfaces, most of the Linux programs are generations behind Windows and macOS, but the thing is: even the best GUIs are less productive than command line interfaces. Terminal is an endless source of productivity, and once you get used to it, there is no way back.</p> <p>As you can see, there is a pragmatic case for open software and replaceable hardware, but it&rsquo;s not the whole story. What does it mean to own a computer? Do you own yours? Those kinds are questions are well addressed by Richard Stallman and if you don&rsquo;t know much about this guy and his ideas, you should probably listen to a few of his speeches. I&rsquo;m not going to repeat all the main FOSS talking points, but I&rsquo;ve been thinking a lot about individual sovereignty in the recent years, and I believe that not being able to fix your hardware or inspect your software is a thing we absolutely shouldn&rsquo;t tolerate. You don&rsquo;t own a thing if you&rsquo;re discouraged to look inside and see what it does. That&rsquo;s the main reason why I use open source software and recommend it to other people.</p> Argon ONE Review https://bubelov.com/blog/2020/argon-one/ Wed, 16 Dec 2020 00:00:00 +0000 https://bubelov.com/blog/2020/argon-one/ <p>Argon ONE is one of the &ldquo;premium&rdquo; cases and its price tag isn&rsquo;t that far from Raspberry Pi 4 itself. In this post, I try to figure out is it worth the cost and how it can improve both system performance and user experience.</p> <p><figure> <a href="https://bubelov.com/blog/2020/argon-one/pi_hu_ae31b57f54588bed.webp"> <img src="https://bubelov.com/blog/2020/argon-one/pi_hu_bb70513815eda101.webp" alt="" /> </a> </figure></p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#why-use-cases">Why Use Cases?</a></li> <li><a href="#assembling-the-case">Assembling the Case</a></li> <li><a href="#argon-one-manual">Argon ONE Manual</a></li> <li><a href="#software-fan-control">Software Fan Control</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="why-use-cases">Why Use Cases?</h2> <p>Raspberry Pi comes without any &ldquo;extras&rdquo;, which means it&rsquo;s unusable unless you have a power supply and other peripherals. Sounds just like a modern smartphone, isn&rsquo;t it? Except the fact that no one tries to bullshit you by attributing this to &ldquo;saving the planet&rdquo;. It&rsquo;s clearly a way to keep the costs down, and it makes you wonder what else you might need to buy in order to get the best Raspberry Pi experience.</p> <p>The minimal requirements are:</p> <ul> <li>Power supply.</li> <li>Storage device (SD card, USB stick, SSD, etc.).</li> </ul> <p>If you have all of those things, your Raspberry Pi will work just fine, but it will look a bit intimidating:</p> <p>That&rsquo;s one of the reasons why some people prefer to put it in a case and there is a huge market for Raspberry Pi cases.</p> <p>Aesthetics alone is a good enough reason to use a case but there are more. Many folks also believe that a good case helps Raspberry Pi to avoid <a href="https://en.wikipedia.org/wiki/Dynamic_frequency_scaling">throttling</a> by keeping it cooler. That&rsquo;s only partly true because it&rsquo;s almost impossible to overheat a Raspberry Pi 4 if it&rsquo;s not overclocked. In fact, using a case can worsen your thermals and slow down your Raspberry Pi.</p> <p>Overclocking is a niche activity, but it has a few interesting use cases, such as turning your Raspberry Pi 4 into a gaming console. It&rsquo;s possible to run Sony PlayStation and Sega Dreamcast games on a Raspberry Pi, but those games tend to test its limits and may cause some noticeable lags, especially if you play for more than an hour.</p> <p>Can a case improve the performance of your Raspberry Pi? Yes, but only the good ones, and they tend to be rather expensive. Argon ONE isn&rsquo;t cheap, and I expect it to be both aesthetically pleasing and capable of sustaining high CPU loads for a long time.</p> <h2 id="assembling-the-case">Assembling the Case</h2> <p>The package looks nice and the instructions are clear.</p> <p><figure> <a href="https://bubelov.com/blog/2020/argon-one/argon-one-assembly-1_hu_c869554f86d4f2be.webp"> <img src="https://bubelov.com/blog/2020/argon-one/argon-one-assembly-1_hu_a9e3e8fbb93d711c.webp" alt="" /> </a> </figure></p> <p>Nothing fancy, just a preview of what&rsquo;s in the box and how it supposed to look like once you assemble it. Note that it has a power button, I really miss this thing when I use Raspberry Pi without a case.</p> <p><figure> <a href="https://bubelov.com/blog/2020/argon-one/argon-one-assembly-2_hu_c47f9d0625aef6fc.webp"> <img src="https://bubelov.com/blog/2020/argon-one/argon-one-assembly-2_hu_984695576e097dfb.webp" alt="" /> </a> </figure></p> <p>Different parts come in different bags and everything seems to be in order.</p> <p><figure> <a href="https://bubelov.com/blog/2020/argon-one/argon-one-assembly-3_hu_27fecf6fefdee352.webp"> <img src="https://bubelov.com/blog/2020/argon-one/argon-one-assembly-3_hu_d027138fd397ce1c.webp" alt="" /> </a> </figure></p> <p>Don&rsquo;t throw away that manual, Argon 40 doesn&rsquo;t provide an online version! That was my first disappointment, by the way.</p> <p><figure> <a href="https://bubelov.com/blog/2020/argon-one/argon-one-assembly-4_hu_39bf5891e0dae994.webp"> <img src="https://bubelov.com/blog/2020/argon-one/argon-one-assembly-4_hu_b60df0c3f257c879.webp" alt="" /> </a> </figure></p> <p>Note that it has a built-in fan. Argon ONE supplies a script which monitors CPU temperature and turns it on only when it&rsquo;s necessary, keeping your Raspberry Pi silent most of the time.</p> <p><figure> <a href="https://bubelov.com/blog/2020/argon-one/argon-one-assembly-5_hu_678edc381e3ffe64.webp"> <img src="https://bubelov.com/blog/2020/argon-one/argon-one-assembly-5_hu_7d0a18637220649f.webp" alt="" /> </a> </figure></p> <p>Pin labels are very much appreciated.</p> <p><figure> <a href="https://bubelov.com/blog/2020/argon-one/argon-one-assembly-6_hu_fc736d9591216e6d.webp"> <img src="https://bubelov.com/blog/2020/argon-one/argon-one-assembly-6_hu_308b4c703afb52c0.webp" alt="" /> </a> </figure></p> <p>The case comes with an extension board which allow us to move all the ports to the back side of the case. This is one of my favorite features of Argon ONE.</p> <p><figure> <a href="https://bubelov.com/blog/2020/argon-one/argon-one-assembly-7_hu_8cc242ed4c135a4c.webp"> <img src="https://bubelov.com/blog/2020/argon-one/argon-one-assembly-7_hu_35322888389abff9.webp" alt="" /> </a> </figure></p> <h2 id="argon-one-manual">Argon ONE Manual</h2> <p>Assembling the case is easy, and it shouldn&rsquo;t take more than a few minutes. I&rsquo;ve scanned the manual, so you can see all the steps involved.</p> <p><figure> <a href="https://bubelov.com/blog/2020/argon-one/argon-one-manual-1_hu_e2da8606fa2b283e.webp"> <img src="https://bubelov.com/blog/2020/argon-one/argon-one-manual-1_hu_6ce40c84e41d62d8.webp" alt="" /> </a> </figure> <figure> <a href="https://bubelov.com/blog/2020/argon-one/argon-one-manual-2_hu_f4b2bb791b62c000.webp"> <img src="https://bubelov.com/blog/2020/argon-one/argon-one-manual-2_hu_b786825f02011cab.webp" alt="" /> </a> </figure> <figure> <a href="https://bubelov.com/blog/2020/argon-one/argon-one-manual-3_hu_63882eeaca4fca20.webp"> <img src="https://bubelov.com/blog/2020/argon-one/argon-one-manual-3_hu_db76977561f8a344.webp" alt="" /> </a> </figure> <figure> <a href="https://bubelov.com/blog/2020/argon-one/argon-one-manual-4_hu_2fbccad99b0cbf93.webp"> <img src="https://bubelov.com/blog/2020/argon-one/argon-one-manual-4_hu_3f614a13cbdf3694.webp" alt="" /> </a> </figure> <figure> <a href="https://bubelov.com/blog/2020/argon-one/argon-one-manual-5_hu_63485d729a6e20ce.webp"> <img src="https://bubelov.com/blog/2020/argon-one/argon-one-manual-5_hu_31c6b62a7fdc7980.webp" alt="" /> </a> </figure> <figure> <a href="https://bubelov.com/blog/2020/argon-one/argon-one-manual-6_hu_739be63cd94a44ba.webp"> <img src="https://bubelov.com/blog/2020/argon-one/argon-one-manual-6_hu_92c877857fdf1a51.webp" alt="" /> </a> </figure> <figure> <a href="https://bubelov.com/blog/2020/argon-one/argon-one-manual-7_hu_ef7dc10bd42909e8.webp"> <img src="https://bubelov.com/blog/2020/argon-one/argon-one-manual-7_hu_a07ef68bba6c26ff.webp" alt="" /> </a> </figure></p> <h2 id="software-fan-control">Software Fan Control</h2> <p>Unfortunately, Argon ONE software works only with Raspberry Pi OS. That&rsquo;s a shame. The way of installing it is also rather shady: you should never run downloaded scripts without carefully checking them first. They may break your system or do something even more nasty.</p> <p>I don&rsquo;t use Raspberry Pi OS, so I was pissed off by the lack of Ubuntu support. Things got better when I found this repo:</p> <p><a href="https://github.com/meuter/argon-one-case-ubuntu-20.04">https://github.com/meuter/argon-one-case-ubuntu-20.04</a></p> <p>The guy who made this alternative installer had tested it with Ubuntu 20.04, but it also worked fine with my Ubuntu 20.10. As I mentioned earlier, you don&rsquo;t need any software to use the fan, but it&rsquo;s pretty loud, and you don&rsquo;t need it working all the time. This script creates a daemon that monitors your CPU temperature and turns on the fan only when the CPU is too hot.</p> <h2 id="conclusion">Conclusion</h2> <p>Using a case can make your Raspberry Pi more aesthetically pleasing, but it can also help you to overclock it and to avoid thermal throttling. For most uses, you don&rsquo;t need to worry about performance.</p> <p>Argon ONE is a nice case, and it&rsquo;s a good fit for a desktop or for a console emulator. Both of those uses are challenging for this $35 computer, and such a case will allow you to overclock it in order to make it more responsive under load.</p> Nextcloud https://bubelov.com/blog/2020/nextcloud/ Tue, 15 Dec 2020 00:00:00 +0000 https://bubelov.com/blog/2020/nextcloud/ <p>I continued using Nextcloud, and it became much more polished this year. It keeps my files and contacts, manages my photos and fetches all the interesting news and podcasts for me. It also has a decent project management functionality, and I use it to collaborate with other people on different projects.</p> <p>Nextcloud has a great potential and practically no competition. It&rsquo;s the easiest way do replace Dropbox and/or Google cloud services such as Google Photos, and I&rsquo;m planning to write a detailed guide on how to set it up and running.</p> Fallout 4 https://bubelov.com/blog/2020/fallout-4-2/ Sat, 28 Nov 2020 00:00:00 +0000 https://bubelov.com/blog/2020/fallout-4-2/ <p>Playing Fallout 4 in Survival mode is a bit tricky in the beginning, but it&rsquo;s not as hard as it may appear if you read about it. Some players find it annoying when the game forces them to satisfy the basic (and most boring) human needs such as hunger, thirst and plenty of sleep. As a person who spent hundreds of hours playing Sims games, it doesn&rsquo;t bother me at all. The only thing that I hate about Survival mode is inability to fast travel. So I thought, until I found out that it&rsquo;s possible to get to any location by a vertibird.</p> <p><figure> <a href="https://bubelov.com/blog/2020/fallout-4-2/vertibird-1_hu_32f22a0ad646e60a.webp"> <img src="https://bubelov.com/blog/2020/fallout-4-2/vertibird-1_hu_cd25b04e6f50524a.webp" alt="" /> </a> </figure></p> <p><figure> <a href="https://bubelov.com/blog/2020/fallout-4-2/vertibird-2_hu_e42ebde894536abb.webp"> <img src="https://bubelov.com/blog/2020/fallout-4-2/vertibird-2_hu_6a1351a2ccbbbb8e.webp" alt="" /> </a> </figure></p> <p>Vertibirds belong to a Brotherhood of Steel and you need to advance in their ranks quite a bit before you&rsquo;ll be able to summon such a fancy air taxi. Having a heavy gun on board adds some extra fun to your rides.</p> <blockquote> <p>Brotherhood of Steel was inspired by a great movie Apocalypse Now (1979).</p> </blockquote> <p><figure> <a href="https://bubelov.com/blog/2020/fallout-4-2/nuka-entrance_hu_b845f1383bc507bb.webp"> <img src="https://bubelov.com/blog/2020/fallout-4-2/nuka-entrance_hu_a4b522b3e77090f2.webp" alt="" /> </a> </figure></p> <p><figure> <a href="https://bubelov.com/blog/2020/fallout-4-2/nuka-thug_hu_869afc7cac6b01dd.webp"> <img src="https://bubelov.com/blog/2020/fallout-4-2/nuka-thug_hu_78697f9e4e9d059a.webp" alt="" /> </a> </figure></p> <p>Nuka-World is one of the official add-ons for Fallout 4. In fact, it&rsquo;s the last official add-on for PS4. I didn&rsquo;t finish all the quests yet, but it has been an interesting experience so far.</p> Raspberry Pi is Vulkan Conformant https://bubelov.com/blog/2020/rpi-vulkan/ Thu, 26 Nov 2020 00:00:00 +0000 https://bubelov.com/blog/2020/rpi-vulkan/ <p>Raspberry Pi sports a pretty capable GPU which helps it to support up to two 4K displays, play 4K video and even run some 3D games with decent frame rates. The problem is, most of the software that needs GPUs doesn&rsquo;t interact with them directly. Instead, it relies on one of the popular graphics APIs such as OpenGL or Vulkan.</p> <p>Let&rsquo;s take Blender as an example. It requires OpenGL 3.3 or Vulkan. In theory, it can run pretty well on a Raspberry Pi 4. In reality, it won&rsquo;t run at all. How so?</p> <p>The thing is, Raspberry Pi 4 GPU drivers can only support OpenGL 3.1 and lower and even this older version of OpenGL wasn&rsquo;t supported for almost a year since Raspberry Pi 4 hit the shelves. There is no point in having good hardware if if doesn&rsquo;t support any modern APIs and that&rsquo;s why I&rsquo;m exited to hear that Raspberry PI GPU drivers are now <a href="https://www.raspberrypi.org/blog/vulkan-update-were-conformant/">Vulkan 1.0 conformant</a>.</p> <p>Vulkan support broadens the range of media manipulation software and games that can be used on a Raspberry Pi 4 and and makes it more appealing as a desktop. Anyway, don&rsquo;t get your hopes up, as Vulkan 1.0 is pretty ancient and Raspberry Pi 4 will never support OpenGL 3.3 due to the limitations of its hardware.</p> State of Bitcoin Mining https://bubelov.com/blog/2020/state-of-bitcoin-mining/ Wed, 25 Nov 2020 00:00:00 +0000 https://bubelov.com/blog/2020/state-of-bitcoin-mining/ <p><a href="https://blog.trezor.io/an-insider-update-on-the-bitcoin-mining-industry-b5fc020a3c1d">https://blog.trezor.io/an-insider-update-on-the-bitcoin-mining-industry-b5fc020a3c1d</a></p> <blockquote> <p>Funds such as the Grayscale Bitcoin Trust, reportedly on track to hold 500,000 bitcoins by the end of the year, have been buying bitcoin at a rate that outpaces the speed at which they are mined.</p> </blockquote> <blockquote> <p>Chinese miners have had large competitive advantages since the ASIC era began in 2014, getting first access to new hardware because manufacturers are based there. Ultimately, the emergence of institutional money is helping drive hashrate redistribution in the west. So, from a mining decentralization standpoint, it’s very good.</p> </blockquote> <blockquote> <p>The new Stratum V2 mining protocol which is included in the OS will eventually grant miners the ability to choose what transactions are included in blocks, known as Job Negotiation, instead of pools choosing, bringing more power to individual miners.</p> </blockquote> Seven Misconceptions About Bitcoin https://bubelov.com/blog/2020/misconceptions-about-bitcoin/ Mon, 23 Nov 2020 00:00:00 +0000 https://bubelov.com/blog/2020/misconceptions-about-bitcoin/ <p>I don&rsquo;t like investment blogs, except this one, of course. And also <a href="https://www.lynalden.com/misconceptions-about-bitcoin">this one</a>. Lyn Alden is an interesting author. Not sure why I like her style of writing, maybe because we both have an engineering background. She mostly writes about the stock market and the benefits of diversification but she is also brave and curious enough to explore less conventional assets such as Bitcoin.</p> High Quality E-Books https://bubelov.com/blog/2020/quality-ebooks/ Fri, 20 Nov 2020 00:00:00 +0000 https://bubelov.com/blog/2020/quality-ebooks/ <p><a href="https://standardebooks.org/">https://standardebooks.org/</a></p> <blockquote> <p>Standard Ebooks is a volunteer driven, not-for-profit project that produces new editions of public domain ebooks that are lovingly formatted, open source, and free.</p> </blockquote> Ansible https://bubelov.com/blog/2020/ansible/ Wed, 18 Nov 2020 00:00:00 +0000 https://bubelov.com/blog/2020/ansible/ <p>I don&rsquo;t have a complex infrastructure to manage but it was still interesting to read on what&rsquo;s going on in this field.</p> <p><a href="https://steampunk.si/blog/getting-started-with-ansible/">https://steampunk.si/blog/getting-started-with-ansible/</a></p> <blockquote> <p>With a market share of 27%, Red Hat’s Ansible has the biggest community to help you propel your development in times of architectural adversity. Being the most versatile CM tool on the market, Ansible is trusted by Intel, Atlassian, Cisco, Twitter, Verizon, and even NASA.</p> </blockquote> Optimizing Things in the USSR https://bubelov.com/blog/2020/optimizing-ussr/ Tue, 10 Nov 2020 00:00:00 +0000 https://bubelov.com/blog/2020/optimizing-ussr/ <p>The author of <a href="https://chris-said.io/2016/05/11/optimizing-things-in-the-ussr">Optimizing Things in the USSR</a> has an interesting angle on the problems which led to the collapse of the Soviet Union. It&rsquo;s not a secret that it collapsed due to a deeply inefficient economic organization but its not obvious why it didn&rsquo;t work out as planned. I mean, it&rsquo;s obvious for the economists, at least from the times of Mises and Hayek but many utopian ideas still sound appealing and achievable. The devil is in the detail.</p> AMD Ryzen 9 on Linux https://bubelov.com/blog/2020/ryzen-9-linux/ Wed, 04 Nov 2020 00:00:00 +0000 https://bubelov.com/blog/2020/ryzen-9-linux/ <p>It looks like new AMD CPUs are <a href="https://www.phoronix.com/scan.php?page=article&amp;item=ryzen-5900x-5950x">working great on Linux</a> from the day one, and they beat Intel in every metric. Maybe it&rsquo;s time to build a new PC?</p> Ubuntu 20.10 and Raspberry Pi 4 https://bubelov.com/blog/2020/ubuntu-20-10-rpi/ Fri, 30 Oct 2020 00:00:00 +0000 https://bubelov.com/blog/2020/ubuntu-20-10-rpi/ <p>As you may have noticed, I&rsquo;m very excited about Raspberry Pi 4. Many people consider it a toy that can&rsquo;t do anything serious but this toy is the most stable and reliable computer in my house. How so?</p> <p>I have two MacBooks, and they are barely working pieces of garbage. I have a Dell XPS 13 laptop running Ununtu and I have many hardware and software issues with it too. All of that fancy hardware is considered high-end and quality stuff. Maybe it&rsquo;s just laptops? Well, I also have a beefy desktop PC with all the bells and whistles. It runs Windows, and it can&rsquo;t even keep a reliable Wi-Fi connection. My PlayStation 4 PRO fails to open its store 4 times out of 5, so I&rsquo;m constantly denied the privilege to buy their overpriced bug-ridden games.</p> <p>All that expensive hardware with up-to-date software fails miserably at the most basic tasks. That&rsquo;s why Raspberry Pi feels like a breath of fresh air. It&rsquo;s simple, it&rsquo;s transparent, and it&rsquo;s reliable. It just works, and it&rsquo;s also quite cheap which makes it hard to regret such a purchase even if you won&rsquo;t find any use for it.</p> <p>Raspberry Pi 4 is my favorite piece of hardware, but it didn&rsquo;t get enough love from the operating systems for a long time. Well, times are changing and Raspberry Pi hardware have got an official support from the Ubuntu developers. Running the latest Ubuntu Desktop and Ubuntu Server is as easy as writing its official image on a USB stick and plugging it into your Raspberry Pi.</p> <p>That&rsquo;s a big deal, really. Try finding a desktop or a laptop with guaranteed Linux support. Most of the retail hardware supports Windows, but it&rsquo;s only the current version of Windows of course. No one is promising that you&rsquo;ll be able to use anything else on such hardware. Same thing happens with the Android smartphones. The manufacturers don&rsquo;t care about the hardware they already sold because they&rsquo;re too busy pushing the new devices as quickly as possible.</p> <p>Raspberry Pi 2 was released in 2015, but you can still use the latest Ubuntu with it. I&rsquo;m not even talking about a theoretical possibility, this thing will work, and it will work well. How can we be so sure? Well, Ubuntu had tasked a few of their internal teams to make sure all the Raspberry Pi models announced since 2015 work flawlessly with this distribution.</p> <p>Ubuntu Desktop tends to be more demanding, thanks to GNOME shell. That means it only works with Raspberry Pi 4, and it requires four or more gigs of RAM. That&rsquo;s annoying, and it says a lot about the inability of programmers to utilize the underlying hardware in an efficient way. That said, Ubuntu folks had confirmed that they&rsquo;re trying to optimize GNOME shell for Raspberry Pi, so we might see some improvements here in the near future.</p> <p>The last thing I want to mention is the long term implications of this new collaboration between Canonical and Raspberry Pi Foundation. The fact that they&rsquo;re in sync gives us hope that we&rsquo;ll get a timely support for all the future Raspberry Pi hardware. That makes Raspberry Pi and Ubuntu a perfect match for anyone who wants to have a cheap and reliable computer that &ldquo;just works&rdquo;.</p> U.S. Justice Department Sues Google https://bubelov.com/blog/2020/google-justice/ Mon, 26 Oct 2020 00:00:00 +0000 https://bubelov.com/blog/2020/google-justice/ <p><a href="https://www.justice.gov/opa/pr/justice-department-sues-monopolist-google-violating-antitrust-laws">https://www.justice.gov/opa/pr/justice-department-sues-monopolist-google-violating-antitrust-laws</a></p> <p>Capitalism can produce cool things, but it&rsquo;s just a tool and every tool has its purpose. We use this tool to better our lives, and it works most of the time. Countries that have free markets are wealthier, healthier and happier than their more restrictive and authoritarian peers.</p> <p>Google was a force for good once but not anymore. It stopped contributing to our well-being, and now it just breaks the rules to satisfy its hunger for ever-increasing profits. We need to take down that beast, and I&rsquo;m glad U.S. Justice Department realizes that.</p> <p>Surprisingly, some people I know still support Google on the grounds that suing it would somehow damage the market and so its anti-capitalist. I believe it&rsquo;s not the case. As Milton Friedman wisely noted, people find it hard to separate pro-business and pro-market actions. Trillion-dollar corporations don&rsquo;t need our support, they have enough money to defend themselves fairly. Not supporting a dirty business isn&rsquo;t anti-market. I&rsquo;m still surprised people confuse those things.</p> Firefox Sucks https://bubelov.com/blog/2020/firefox-sucks/ Sat, 24 Oct 2020 00:00:00 +0000 https://bubelov.com/blog/2020/firefox-sucks/ <p>It&rsquo;s been an interesting year for Firefox. They fired a lot of staff while rising the CEO compensation and now they&rsquo;re barking at U.S. Justice Department to satisfy their true master: Google. Not cool, Firefox, not cool.</p> <p><a href="https://blog.mozilla.org/blog/2020/10/20/mozilla-reaction-to-u-s-v-google/">https://blog.mozilla.org/blog/2020/10/20/mozilla-reaction-to-u-s-v-google/</a></p> <p><a href="https://drewdevault.com/2020/10/22/Firefox-the-embarassment-of-FOSS.html">https://drewdevault.com/2020/10/22/Firefox-the-embarassment-of-FOSS.html</a></p> Uber Pressures its Workers https://bubelov.com/blog/2020/uber-pressure/ Tue, 20 Oct 2020 00:00:00 +0000 https://bubelov.com/blog/2020/uber-pressure/ <p>Here is <a href="https://news.bloomberglaw.com/daily-labor-report/uber-drivers-sue-company-over-pressure-to-support-prop-22">another example</a> of a big corporation doing nasty things and manipulating numbers to satisfy its greed. Asking workers about their loyalty inside the closed source app that is able to collect that data and punish disloyal employees, what might go wrong? Looks like a pretty normal thing to do, absolutely no reasons to think that those answers were given under pressure.</p> Getting Started With Self-Hosting https://bubelov.com/blog/2020/getting-started-with-self-hosting/ Sun, 11 Oct 2020 00:00:00 +0000 https://bubelov.com/blog/2020/getting-started-with-self-hosting/ <p>Self-hosting is a broad concept and there are many ways to do it right and even more ways to do it wrong. In this article, I cover the basic concepts of self-hosting and the easiest ways to start this challenging journey to your digital sovereignty.</p> <p><figure> <a href="https://bubelov.com/blog/2020/getting-started-with-self-hosting/thumb_hu_e05ac8cc653bfdd4.webp"> <img src="https://bubelov.com/blog/2020/getting-started-with-self-hosting/thumb_hu_48a09700d92c2b5e.webp" alt="" /> </a> <figcaption>Illustration by <a href="https://unsplash.com/@floriankrumm">Florian Krumm</a></figcaption> </figure></p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#what-is-self-hosting">What is Self-Hosting?</a></li> <li><a href="#what-is-a-server">What is a Server?</a></li> <li><a href="#why-do-i-need-a-server">Why do I Need a Server?</a></li> <li><a href="#how-to-choose-a-server">How to Choose a Server</a></li> <li><a href="#recommended-server-raspberry-pi-4">Recommended Server: Raspberry Pi 4</a></li> <li><a href="#how-to-choose-an-operating-system">How to Choose an Operating System</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="what-is-self-hosting">What is Self-Hosting?</h2> <p>There are no clear definitions so let me define what I mean by self-hosting:</p> <blockquote> <p>Self-hosting is a process of setting up and maintaining your own servers.</p> </blockquote> <p>I&rsquo;ll define &ldquo;server&rdquo; a bit later but lets unpack &ldquo;maintenance&rdquo; first. By maintenance I mean at least:</p> <ul> <li>Providing adequate level of security (firewall, logging, monitoring, etc).</li> <li>Knowing your software and being able to configure it.</li> <li>Keeping your software is up-to-date.</li> <li>Dealing with the hardware issues (when using your own hardware).</li> </ul> <p>Those tasks aren&rsquo;t hard but they are consequential. If you aren&rsquo;t ready to learn how to do it, self-hosting is not for you.</p> <p>You can also read this post where I describe certain less technical reasons to set up your own server:</p> <p><a href="https://bubelov.com/blog/2020/selfhosting/">Self-Hosting is a Productive Hobby</a></p> <h2 id="what-is-a-server">What is a Server?</h2> <p>As you may have guessed already, server is a broad concept too. In short, it makes sense to split certain programs in two parts: client and server. There are many reasons for that. Let&rsquo;s take OpenStreetMap as an example. The whole world map is too big to keep it on your smartphone. It makes total sense to keep the entire map on a server and to give clients only the parts of that map that were requested by the user.</p> <p>So, server is just a role, and it&rsquo;s applicable to both hardware and software. There is a popular misconception that servers are always &ldquo;somewhere else&rdquo; but nothing stops us from running both client and server software on the same piece of hardware. In fact, it happens pretty often. In the context of this article, we&rsquo;re going to focus on the server software which is located in a particular place: your dedicated server hardware.</p> <p>Here are a few examples of server hardware:</p> <ul> <li>Raspberry Pi 4 running in your house.</li> <li>Virtual server from Scaleway, Digital Ocean or any other provider.</li> <li>Your old dusty laptop or a PC which you no longer use.</li> </ul> <p>What do they have in common? They&rsquo;re all capable of running software and that&rsquo;s all we need from a server. You can also connect them to your LAN end even make them accessible from the Internet (and it&rsquo;s not always a good thing).</p> <h2 id="why-do-i-need-a-server">Why do I Need a Server?</h2> <p>Servers may do many things but the most popular one is data storage. One of the benefits of cloud file storage services such as Dropbox is the fact that it makes it harder to lose your data. Computers are unreliable and your hard drive or an SSD might fail at any moment, and you may end up losing all of your data. Dropbox solves this problem by keeping all of your data on their servers so you can always request their copy of your data if something happens with your own local replica.</p> <p>Servers store and process your personal data and that can be very useful. Unfortunately, collecting and selling your personal data is a booming business and companies simply can&rsquo;t resist doing that. Even worse, companies that collect a lot of sensitive data for a lot of people are perfect targets for many hackers and most companies are very bad at protecting the data they collect. That&rsquo;s not good: your data is constantly being sold or stolen and there is nothing you can do about it as long as you keep relying on someone else&rsquo;s servers. That&rsquo;s why it&rsquo;s very important to understand who owns the servers you rely on and what incentives influence their actions.</p> <h2 id="how-to-choose-a-server">How to Choose a Server</h2> <p>First thing you have to figure out is location. Let&rsquo;s say you want to host your own Dropbox alternative. Nextcloud offers same features and many more, and you can install it on your own server. Running Nextcloud server from home can be a good idea. Here is why:</p> <ul> <li> <p><strong>Speed</strong>: You can move data much faster when it happens within your home network. Most current routers offer Gigabit Ethernet and pretty fast Wi-Fi. It&rsquo;s important to move files fast, isn&rsquo;t it?</p> </li> <li> <p><strong>Latency</strong>: High speed is important for moving large files but certain kinds of client-server communications are more sensitive to so-called &ldquo;response time&rdquo;. Data can&rsquo;t travel faster than the speed of light, which means that even if you have the best Internet connection possible, having a server far from your house will slow things down quite a bit.</p> </li> <li> <p><strong>Privacy</strong>: Data centers have fewer incentives to touch your data, so I wouldn&rsquo;t worry too much about using a virtual server from a well known and reputable provider but keeping your servers and your data at home eliminates the need to trust the data centers and that&rsquo;s a good thing.</p> </li> <li> <p><strong>Cost</strong>: Cloud servers are cheap but cloud storage is pretty expensive. Let&rsquo;s say you need to keep 2TB of family photos and other heavy but important files. This will cost you a lot to rent this space in the cloud and you&rsquo;ll need to double those expenses if you need a backup (and you really need a backup). On the other hand, HDDs and SSDs are cheap and they will last your for years, with no monthly bills!</p> </li> </ul> <p>Self-hosting from home has its benefits but there are certain downsides as well:</p> <ul> <li> <p><strong>Maintenance</strong>: Buying hardware and dealing with hardware failures takes time and effort. Renting a virtual server is pretty much effortless.</p> </li> <li> <p><strong>Reliability</strong>: I bet that the internet connection and an electricity supply in your house are less reliable than in an average data center. Hosting a popular blog from home might not be a good idea so you should consider your tolerance for service interruptions.</p> </li> </ul> <h2 id="recommended-server-raspberry-pi-4">Recommended Server: Raspberry Pi 4</h2> <p>I tend to keep my articles practical, and it means I can&rsquo;t be too abstract, and I have to make certain choices. When I make those choices, I try to explain the reasons behind them. One of those choices is using Raspberry Pi 4 as a server. Here is why:</p> <ul> <li> <p><strong>It&rsquo;s reproducible</strong>: You might have an old Dell laptop that can be transformed into a nice server but there aren&rsquo;t many people in the same situation. Advising people to buy an old second hand laptop is also problematic for various reasons. On the other hand, Raspberry Pi is widely available and still up to date. It&rsquo;s easy to set up, and it has a first-class Linux support, so you won&rsquo;t have any driver issues.</p> </li> <li> <p><strong>It&rsquo;s cheap</strong>: It starts from $35. Very much worth it, especially for a beginner who can easily get stuck with trying to install Linux on an incompatible or poorly supported piece of hardware.</p> </li> <li> <p><strong>It&rsquo;s reasonably fast</strong>: If all of the above sounds too good to be true, there is a reason for that: $35 server will have the performance of a $35 server, and it&rsquo;s not the highest performance possible. Literally any laptop or a PC would run the same programs much faster. In my opinion, you should consider switching to something more powerful once you&rsquo;re comfortable enough with maintaining your own servers but Raspberry Pi 4 can easily run most of the apps you might need and the performance should be OK.</p> </li> <li> <p><strong>Dealing with hardware issues is easy</strong>: Just replace the whole board with the identical one. That&rsquo;s fast and effective. It can be done in seconds, just remove the SD card and connect it to other board. If you have any USB peripherals, connect them too and that&rsquo;s it, everything will work just fine.</p> </li> </ul> <h2 id="how-to-choose-an-operating-system">How to Choose an Operating System</h2> <p>Raspberry Pi computers have their own official Linux distribution: <a href="https://en.wikipedia.org/wiki/Raspberry_Pi_OS">Raspberry Pi OS</a>. In my opinion, it&rsquo;s not that good for a server, and it&rsquo;s not its main focus anyway. It&rsquo;s more of a desktop operating system aimed at teaching kids how to program which means it lacks some tools that are aimed at different audiences.</p> <p><a href="https://ubuntu.com/download/raspberry-pi">Ubuntu</a> is the most popular Linux distribution, and it works like a charm on a Raspberry Pi, so I would recommend it as a solid foundation for your self-hosting needs. It also provides a nice layer of hardware abstraction in a sense that you can easily migrate from a Raspberry Pi 4 to any other computer or rent a virtual server. Chances are, all of those options would work well with Ubuntu, so you won&rsquo;t need to learn from scratch how to configure and maintain your new server.</p> <h2 id="conclusion">Conclusion</h2> <p>Self-hosting is not easy, and it was a privilege only a few could enjoy. Today, it&rsquo;s easier than ever to self-host your own server. Raspberry Pi is easy get started with, even if you don&rsquo;t have a lot of time. It has a huge focus on kids and education and it says something about how approachable it is if you&rsquo;re interested enough. Linux isn&rsquo;t a scary beast anymore and Ubuntu is a well polished and well documented distribution. Using containers and Docker makes deploying software easy and fun, so things seem to get better and I hope that it gives more people the ability to use self-hosting as a way to control their own digital stuff.</p> Self-Hosting as a Hobby https://bubelov.com/blog/2020/selfhosting/ Mon, 05 Oct 2020 00:00:00 +0000 https://bubelov.com/blog/2020/selfhosting/ <p>Most hobbies have certain practical benefits, but our free time is limited, so we should pick our hobbies wisely. In this article, I share my thoughts and experiences on self-hosting my own digital infrastructure for fun and profit.</p> <p><figure> <a href="https://bubelov.com/blog/2020/selfhosting/thumb_hu_c4bcae3c307b1411.webp"> <img src="https://bubelov.com/blog/2020/selfhosting/thumb_hu_738f54a89d9dc137.webp" alt="" /> </a> <figcaption>Illustration by <a href="https://unsplash.com/@sandramode">Sandra Ahn Mode</a></figcaption> </figure></p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#why-self-host">Why Self-Host?</a></li> <li><a href="#security-without-transparency">Security Without Transparency</a></li> <li><a href="#distance-and-empathy">Distance and Empathy</a></li> <li><a href="#do-service-providers-deserve-your-trust">Do Service Providers Deserve Your Trust?</a></li> <li><a href="#companies-come-and-go">Companies Come and Go</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="why-self-host">Why Self-Host?</h2> <blockquote> <p>What more sacred, what more strongly guarded by every holy feeling, than a man&rsquo;s own home? ― Cicero</p> </blockquote> <p>Surveillance capitalism is a relatively new term, and it used to describe a system where companies can track our activity and steal our personal data with the intent of selling it to the highest bidder. Many of us were also rightfully outraged when we learned about illegal and immoral surveillance programs waged by our governments.</p> <p>The importance of digital privacy and being in control of your own data are the long-standing priorities of the open source community. All of those ideas aren&rsquo;t new, but most people didn&rsquo;t take it seriously until 2013 when the world was shaken by Snowden&rsquo;s revelations.</p> <p>The information which Snowden shared with the world was a wake-up call for many, but we&rsquo;re still struggling to figure out the best ways of limiting widespread digital surveillance. Self-hosting can be considered an extreme form of fighting digital surveillance, since it allows you to keep all of your data locally without exposing it to any third parties.</p> <p>Setting up, securing and maintaining your own server is the best way to protect your data, but it takes time, and you have to learn a lot before you can even start. Also, we&rsquo;re constantly bombarded by all sorts of a corporate propaganda which talks lengths about how easy it is to start using their products. Not surprisingly, they aren&rsquo;t that eager to tell you about their shady but very profitable practices such as collecting as much of your data as possible and selling it to the highest bidder.</p> <p>It&rsquo;s a shame that tech companies spy on us, but the incentives are too tempting. Now, let&rsquo;s try to compare self-hosted open source software and &ldquo;managed&rdquo; or purely commercial closed-source products in order to figure out the pros and cons of each option and whether self-hosting is a thing worth trying.</p> <h2 id="security-without-transparency">Security Without Transparency</h2> <p>Most of the apps and websites we use are &ldquo;black boxes&rdquo; in a sense that it&rsquo;s extremely hard and, in many cases, impossible, to figure out what they&rsquo;re doing with your phone, laptop or any other piece of hardware you happen to run them on. It&rsquo;s fascinating how much we have to trust the software companies and how eager they are to betray this trust if it gives them an opportunity to make an extra buck.</p> <p>Imagine a fast food chain which has an option of making a huge profit by adding a little dose of poison to each meal served. It will most likely pass on that opportunity because we can expose it by auditing the food, and we also have certain laws which are supposed to make sure that anyone involved in such a scheme will end up in jail. Food is transparent in a sense that it can be examined with ease, and it&rsquo;s really hard to get away with harming your customers. So, what are the incentives here?</p> <ul> <li> <p>Law – Breaking the law is the fastest way to lose your business and end up in jail</p> </li> <li> <p>Profit – Poisoning food isn&rsquo;t profitable, therefore it&rsquo;s a pretty dumb thing for a company to do</p> </li> <li> <p>Ethics – Harming people is bad, right?</p> </li> </ul> <p>Well, this example is a bit silly, isn&rsquo;t it? We need to focus on the real world instead of an imaginary fast food chain operated by psychopaths. Let&rsquo;s look at Big Tobacco from the same angles:</p> <ul> <li> <p>Law – They sold and advertised tobacco products knowing that it&rsquo;s a deadly poison until it became illegal not to tell about it</p> </li> <li> <p>Profit – Why bother telling your customers about the risks associated with using your product, that would hurt the bottom line</p> </li> <li> <p>Ethics – No such thing, it&rsquo;s all about money</p> </li> </ul> <p>By using closed source software which is hosted somewhere &ldquo;in the cloud&rdquo; we invite companies to act on their darkest incentives because it&rsquo;s very profitable, and it&rsquo;s really easy to conceal such activities. Most apps and websites generate small amounts of &ldquo;poison&rdquo; every time we interact with them and this poison slowly accumulates in various databases where it can be enhanced and used to harm us later. You see, this poison is personalized. It can&rsquo;t be used on a random person, it works only on a specific person and using the right poison on the right target is an extremely profitable activity.</p> <p>By poison, I mean personally identifiable data, of course. Does it seem too far-fetched? Let&rsquo;s say you&rsquo;ve got something valuable, and you want to buy a safe. Do you really want this fact to be known to anyone? What if this information can be cross-referenced with your real address? What if we can add some other data points such as your usual time of absence from home and generate a list of targets which would be pretty valuable to various criminals? What about state actors? Even the NSA don&rsquo;t mind breaking the law and invading privacy of their own citizens. Those guys are angels compared to what less developed nations tend to do with people they don&rsquo;t like and wish to get rid of.</p> <p>It doesn&rsquo;t matter who collects your data. The more data your leak, the more vulnerable you are. Companies use it to influence your consumer choices, politicians use it to influence your votes, state actors use it to deal with the dissidents, criminals need it to steal from you or blackmail you and literally no one is harvesting your data in order to make your life better: there are no incentives to do that.</p> <p>Self-hosting is the best way to protect your data and to have a transparent infrastructure built by honest people and for honest people.</p> <h2 id="distance-and-empathy">Distance and Empathy</h2> <p>Let&rsquo;s say you&rsquo;re a local baker. You know most of your customers personally. Many of them are friends of yours. We have much more empathy for the real people who we know personally than for the abstract &ldquo;users&rdquo;, and it affects our decisions in a big way.</p> <p>We can&rsquo;t even keep more than a couple of hundred people in our heads. Human communities don&rsquo;t scale that well and humans aren&rsquo;t known for being friendly and empathetic to total strangers. I believe that, contrary to the popular opinion, there is nothing glorious in scale. I&rsquo;ve met many talented technologists who&rsquo;re excited about scaling systems and building tools that help them achieve that, and I get it: building scalable and reliable systems is hard and talented people tend to like challenge. Of course, the likes of Google, Amazon or Facebook are scalability marvels but the problem is: they&rsquo;re also merciless monopolies that aren&rsquo;t shy of exploiting their scale to crush their rivals. Would it be beneficial for a society to let those companies get even bigger?</p> <p>The bigger the company, the less it cares about an individual customer, but at the same time, their customers become increasingly more dependent on their new digital overlords. It&rsquo;s easy to ban a YouTube blogger for any bullshit reason or even for no reason at all. It means nothing for a big company which owns the platform, but it can be devastating for its unfortunate victims.</p> <p>When I hear &ldquo;scalable&rdquo; I tend to add &ldquo;and also prone to centralization&rdquo; in my mind. As engineers, we often obsess about technical challenges and tend not to think about the social consequences of our actions. Monopolies are bad for consumers, they don&rsquo;t care about consumers, and it&rsquo;s also pretty bad to support them by using their products or by working for them. As for challenges, there is a lot of inherently complex problems which don&rsquo;t involve scaling monsters to monopolistic sizes. It&rsquo;s even possible to scale stuff without causing centralization, take a look at Bitcoin if you need an example.</p> <p>Self-hosting can remove your dependency on big platforms, allowing you not to participate in making big tech even bigger and nastier.</p> <h2 id="do-service-providers-deserve-your-trust">Do Service Providers Deserve Your Trust?</h2> <p>We all have to deal with the fact that the future is uncertain. Even the most trusted companies may decide to prioritize their short-term profits over their long-term reputation. It&rsquo;s called reputation mining and a low level of transparency allows software companies to slow down their reputation rot, adding more incentives to engage in the behavior which damages their clients but pushes up their profits.</p> <p>Self-hosting helps you to avoid disappointment when a yet another well known and adored brand becomes evil profit-at-all-costs monstrosity. Don&rsquo;t make a mistake of trusting commercial companies too much. Even the best ones tend to rot by themselves or with the help of activist investors that can take them over at any time.</p> <h2 id="companies-come-and-go">Companies Come and Go</h2> <p>I worked for many big companies as well as for several small startups. Most of those big companies are still in business, but they&rsquo;re filled with ads and deceptive dark patterns. They&rsquo;ve become user-hostile and their users can&rsquo;t do anything about it. Most of the startups I&rsquo;ve worked for have failed, shutting down their servers and abandoning their users. That&rsquo;s what happens when you rely on a service you don&rsquo;t actually own.</p> <p>I really liked Google Inbox and I used it as an email client and task manager. It was a great service and it was also pretty popular. Guess what? Google killed it and tried to force all of its users to switch to Gmail which is a piece of garbage, and it&rsquo;s full of ads. Can you opt out? Of course not. I&rsquo;m not ready to self-host my emails, but it taught me a lesson, and I switched to an email service which is transparent and upfront about its business model. It uses encryption to provably minimize the amount of data which is accessible from its servers, which is a nice feature to have. Anyway, this situation was a good reminder that the costs of NOT self-hosting your services can also be pretty high.</p> <p>Self-hosting allows you to use the software you like for as long as you wish to.</p> <h2 id="conclusion">Conclusion</h2> <p>I tend to be optimistic about most of the things. Well, <a href="https://www.youtube.com/watch?v=MBRqu0YOH14">optimisticly-nihilistic</a>, which means I tend not to give a fuck about most of the online and offline dramas but, for some reason, resisting data harvesting and raising awareness of these shady practices seems like a right thing to do.</p> <p>It&rsquo;s hard to argue that automation and the division of labor are wrong, and it&rsquo;s not what I wanted to imply here. There are reasons why many of the services we depend on are centralized and why self-hosting is not for everyone. It has many benefits, but you need to invest some time and money in order to enjoy those benefits.</p> <p>I see self-hosting as a productive hobby, similar to upgrading and customizing your house. I would argue that our digital spaces are no less important than our physical spaces and taking care of your digital castle and taking time to understand how it works might be both productive and fun. Isn&rsquo;t it cool to have an autonomous house which has its own water and energy supply? In my view, self-hosting enables us to build self-sustainable digital fortress, what&rsquo;s not to like about it?</p> Trezor Suite https://bubelov.com/blog/2020/trezor-suite/ Sun, 04 Oct 2020 00:00:00 +0000 https://bubelov.com/blog/2020/trezor-suite/ <p>Good news from Trezor Bitcoin wallet. I taught a few people on how to use Trezor and one of the hardest parts to explain is finding the wallet web interface. People tend to type &ldquo;trezor&rdquo; in a search box and click on the first link in their search results. That&rsquo;s not a good idea since this search result could lead to a fake website and trick them into sending their bitcoins to criminals.</p> <p><a href="https://suite.trezor.io">Trezor Suite</a> is a desktop app so there is no browser to open. This makes the whole process of managing bitcoins safer and easier which not only benefits the existing users but also makes Bitcoin more approachable for a wider audience. SatoshiLabs is a great ethical company that creates wonderful hardware and software. Quite a rare bird nowadays.</p> Fallout 4 https://bubelov.com/blog/2020/fallout-4/ Fri, 02 Oct 2020 00:00:00 +0000 https://bubelov.com/blog/2020/fallout-4/ <p>I pre-ordered Fallout 4 and I finished it for the first time several days after its initial release. It&rsquo;s a great game, in general, and it&rsquo;s what you expect form a Fallout game made by Bethesda, with all the good and bad parts.</p> <p>There is something extremely appealing in this post-apocalyptic world. I spent a lot of time as a child exploring the remains of Soviet Empire and I still like to explore abandoned collective farms and industrial buildings when I&rsquo;m visiting Russia. It looks so distant, like the artifacts from another world. Fallout games feel similar, it lets you explore the ruing of something more complex and mysterious. It&rsquo;s like exploring a new world you know very little about.</p> <p><figure> <a href="https://bubelov.com/blog/2020/fallout-4/fallout-1_hu_8b3e5e9deb3c4b85.webp"> <img src="https://bubelov.com/blog/2020/fallout-4/fallout-1_hu_2644387a27a1026a.webp" alt="" /> </a> </figure></p> <p>This time, I&rsquo;ve decided to try the so-called survival mode. That adds a few interesting twists in the gameplay. It simulates hunger and thirst, and you can even get sick for various reasons. You can save the game only if you&rsquo;re nearby a bed and yeah, you need to sleep too. If it&rsquo;s not challenging enough, the damage from your enemies is much higher and a couple of bullets or a single Molotov cocktail is enough to kill you.</p> <p><figure> <a href="https://bubelov.com/blog/2020/fallout-4/fallout-2_hu_8895d96c0a43cf6.webp"> <img src="https://bubelov.com/blog/2020/fallout-4/fallout-2_hu_161d8e7fa226855e.webp" alt="" /> </a> </figure></p> <p>If the survival mode isn&rsquo;t challenging enough: Bethesda is always ready to add some real challenges. The thing is, Bethesda games are full of bugs, and they tend to crash a lot. This is more or less tolerable when you have an auto save in a normal mode, but it&rsquo;s extremely frustrating in survival mode. Hard to blame Bethesda, they only had five years to fix this fucking game.</p> The World Needs Another Feed Reader https://bubelov.com/blog/2020/another-feed-reader/ Wed, 30 Sep 2020 00:00:00 +0000 https://bubelov.com/blog/2020/another-feed-reader/ <p>Okay, maybe it&rsquo;s just me. Anyway, here is the source code:</p> <p><a href="https://github.com/bubelov/news">https://github.com/bubelov/news</a></p> <p>I&rsquo;ve been working on this thing for the last few weeks, and it&rsquo;s still a bit rough around the edges, but I already use it, and it serves me well. I tried to post it to Google Play, but it seems like Google is trying to force the developers to use Chrome instead of Firefox, again:</p> <p><figure> <a href="https://bubelov.com/blog/2020/another-feed-reader/google-play-console_hu_ee2172fec5e98529.webp"> <img src="https://bubelov.com/blog/2020/another-feed-reader/google-play-console_hu_a769546cf5c25942.webp" alt="" /> </a> </figure></p> <p>It only works with Nextcloud, but I&rsquo;m planning to add a standalone mode in the coming weeks.</p> PostgreSQL 13 https://bubelov.com/blog/2020/postgres-13/ Wed, 23 Sep 2020 00:00:00 +0000 https://bubelov.com/blog/2020/postgres-13/ <p>PostgreSQL is my favorite database, and it just got a new major update. Here are some of the new features that I find interesting:</p> <ul> <li> <p>Built in UUID generation: It always bothered me that I have to add <code>uuid-ossp</code> extension on order to be able to generate UUIDs. With the introduction of the built in <code>gen_random_uuid</code> function, there is no need to use that extension anymore. This is my favorite change since I don&rsquo;t maintain large databases, so I won&rsquo;t notice many other improvements.</p> </li> <li> <p>B-Tree Deduplication: This feature reduces the size of your indices. It&rsquo;s not that important if you have a bunch of small tables, but it&rsquo;s certainly good news for anyone who stores a lot of data in their databases.</p> </li> <li> <p>Parallel VACUUM: Now it&rsquo;s possible to clean several indices simultaneously. Again, you need to have a lot of tables with a lot of indices to benefit from this feature.</p> </li> </ul> <p>Useful links:</p> <p><a href="https://www.postgresql.org/about/news/2077/">https://www.postgresql.org/about/news/2077/</a></p> <p><a href="https://pganalyze.com/blog/postgres13-better-performance-monitoring-usability">https://pganalyze.com/blog/postgres13-better-performance-monitoring-usability</a></p> History of Object-Oriented Programming https://bubelov.com/blog/2020/history-of-oop/ Sun, 20 Sep 2020 00:00:00 +0000 https://bubelov.com/blog/2020/history-of-oop/ <p>I enjoyed reading <a href="https://www.sciencedirect.com/science/article/pii/S0890540113000795">this article</a> and is sparked my interest in the history of different programming paradigms.</p> <p>OOP is a dominant approach to software engineering but not everyone likes it and there are good reasons behind many of its criticisms. It&rsquo;s easy to dismiss stuff we don&rsquo;t like, but it&rsquo;s more interesting to try to understand why some people have different opinions and why they come with certain ways of solving their problems.</p> <p>I spent many years working on Java and Kotlin programs and of course I love OOP, although I never read about its roots. Not that I found anything unexpected in this article, except the fact that objects were supposed to be &ldquo;active&rdquo; which is pretty odd by modern standards. It got me thinking about other programming paradigms and the reasons why their inventors created them. I certainly should experiment more with other programming paradigms and read about their history, it seems like a lot of fun. Passing Lisp exams in my university wasn&rsquo;t that fun, by the way.</p> Getting Familiar With nftables https://bubelov.com/blog/2020/nftables/ Fri, 18 Sep 2020 00:00:00 +0000 https://bubelov.com/blog/2020/nftables/ <p>The more I experiment with <code>nftables</code>, the more I like it. It&rsquo;s not high level enough to be a black box Voodoo magic, and it&rsquo;s not low level enough to be a royal pain in the ass.</p> <p>Let&rsquo;s look at the following example:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/usr/sbin/nft -f </span></span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl">flush ruleset </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1">### CHAINS</span> </span></span><span class="line"><span class="cl"><span class="c1"># INPUT - all incoming packets are checked against the rules in this chain</span> </span></span><span class="line"><span class="cl"><span class="c1"># OUTPUT - all outgoing packets are checked against the rules in this chain</span> </span></span><span class="line"><span class="cl"><span class="c1"># FORWARD - all packets being sent to another computer are checked against the rules in this chain</span> </span></span><span class="line"><span class="cl"><span class="c1">###</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1">### CT STATES</span> </span></span><span class="line"><span class="cl"><span class="c1"># NEW - the packet has started a new connection</span> </span></span><span class="line"><span class="cl"><span class="c1"># ESTABLISHED - the packet is associated with a connection which has seen packets in both directions</span> </span></span><span class="line"><span class="cl"><span class="c1"># RELATED - the packet is starting a new connection, but is associated with an existing connection</span> </span></span><span class="line"><span class="cl"><span class="c1">###</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl">table inet filter <span class="o">{</span> </span></span><span class="line"><span class="cl"> chain input <span class="o">{</span> </span></span><span class="line"><span class="cl"> <span class="nb">type</span> filter hook input priority 0<span class="p">;</span> policy drop<span class="p">;</span> </span></span><span class="line"><span class="cl"> iif lo accept comment <span class="s2">&#34;Accept localhost traffic&#34;</span> </span></span><span class="line"><span class="cl"> ct state established,related accept comment <span class="s2">&#34;Accept traffic originated from here&#34;</span> </span></span><span class="line"><span class="cl"> tcp dport ssh accept comment <span class="s2">&#34;Accept SSH traffic&#34;</span> </span></span><span class="line"><span class="cl"> <span class="o">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> chain output <span class="o">{</span> </span></span><span class="line"><span class="cl"> <span class="nb">type</span> filter hook output priority 0<span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="o">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1"># Drop everything, it&#39;s not a router</span> </span></span><span class="line"><span class="cl"> chain forward <span class="o">{</span> </span></span><span class="line"><span class="cl"> <span class="nb">type</span> filter hook forward priority 0<span class="p">;</span> policy drop<span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="o">}</span> </span></span><span class="line"><span class="cl"><span class="o">}</span> </span></span></code></pre></div><p>Not much code to describe a firewall ruleset! It&rsquo;s also easy to read and modify. All the action happens in the <code>input</code> chain which is responsible for filtering all the incoming traffic. It starts with a <code>default-deny</code> mode which means it doesn&rsquo;t allow any incoming traffic to pass unless you add a special rule that allows certain kinds of traffic under certain conditions.</p> <p>First, I wanted to allow any traffic coming from the loopback interface:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1">## iif = &#39;if input interface is ...&#39;</span> </span></span><span class="line"><span class="cl"><span class="c1">## lo = loopback interface</span> </span></span><span class="line"><span class="cl">iif lo accept comment <span class="s2">&#34;Accept localhost traffic&#34;</span> </span></span></code></pre></div><p>Next, it would be nice to tell the firewall to accept any traffic sent to this computer as a response to locally initiated requests:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1">### CT STATES</span> </span></span><span class="line"><span class="cl"><span class="c1"># NEW - the packet has started a new connection</span> </span></span><span class="line"><span class="cl"><span class="c1"># ESTABLISHED - the packet is associated with a connection which has seen packets in both directions</span> </span></span><span class="line"><span class="cl"><span class="c1"># RELATED - the packet is starting a new connection, but is associated with an existing connection</span> </span></span><span class="line"><span class="cl"><span class="c1">###</span> </span></span><span class="line"><span class="cl">ct state established,related accept comment <span class="s2">&#34;Accept traffic originated from here&#34;</span> </span></span></code></pre></div><p>And finally, it would be nice to have an SSH port open if you&rsquo;re dealing with the remote machine. Shutting yourself from your own server can be a costly mistake, and it&rsquo;s not uncommon. Here is how to allow SSH traffic, assuming your SSH daemon is set to listen on a well known port 22 (which you should change, by the way):</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">tcp dport ssh accept comment <span class="s2">&#34;Accept SSH traffic&#34;</span> </span></span></code></pre></div><p>If you don&rsquo;t want to get spammed by unwelcome SSH login attempts, just set your SSH daemon to listen to another port. Let&rsquo;s say our SSH daemon is listening on the port 40736. That would mean that we need to add the following rule:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">tcp dport <span class="m">40736</span> accept comment <span class="s2">&#34;Accept SSH traffic&#34;</span> </span></span></code></pre></div><p>If you decide to host an HTTP server on this machine, just add http and https ports to that rule:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">tcp dport <span class="o">{</span> 40736, http, https <span class="o">}</span> accept comment <span class="s2">&#34;Accept SSH and HTTP(S) traffic&#34;</span> </span></span></code></pre></div><p>That&rsquo;s it. Opening additional ports is trivial and the documentation is good. I&rsquo;m just starting with this thing, and I&rsquo;m planning to experiment with the logging and tracing tools in the following weeks.</p> Bloodborne https://bubelov.com/blog/2020/bloodborne/ Mon, 07 Sep 2020 00:00:00 +0000 https://bubelov.com/blog/2020/bloodborne/ <p><figure> <a href="https://bubelov.com/blog/2020/bloodborne/bloodborne-1_hu_add585d26b4194e1.webp"> <img src="https://bubelov.com/blog/2020/bloodborne/bloodborne-1_hu_5ec206d32d89f49.webp" alt="" /> </a> </figure></p> <p>I never played Souls games before, but I heard from a few friends that Bloodborne is a new Dark Souls clone. Personally, I tend not to label new games as clones since every game is different, but the fact that it might have something worth cloning was encouraging. Dark Stone was often described as a Diablo clone, but I truly believe that is much better than Diablo despite having a lot of good things in common.</p> <p>That said, I don&rsquo;t know if Bloodborne is worth your time or if it&rsquo;s better to play something more original. What I can say for sure is that Bloodborne is fucking amazing. It has this tough love appeal: it&rsquo;s absolutely merciless to the player, and it&rsquo;s terrible at explaining how to play this damn game, but it makes it even more interesting somehow.</p> <p>Bloodborne aesthetic is extremely dark and gothic. Religion and church are the common themes which is rather unexpected from a Japanese studio. What I like about video games is the fact that they keep innovating. I get that this game is just a part of the Souls genre but this genre by itself is pretty new. I can&rsquo;t name any new and interesting literary genres since cyberpunk, for example, and cyberpunk was invented many decades ago. Video games tend to have stories, but they give their authors many other tools to experiment with, leading to a crazy amount of wild combinations which are sufficiently different from each other.</p> <p><figure> <a href="https://bubelov.com/blog/2020/bloodborne/bloodborne-2_hu_e5b3f01ce6efa52d.webp"> <img src="https://bubelov.com/blog/2020/bloodborne/bloodborne-2_hu_9222a20439f489d1.webp" alt="" /> </a> </figure></p> <p>I don&rsquo;t want to spoil the story, but you have to fight a lot, level up in an unusual way, and you should be ready to die countless times. All of that wrapped in bizarre gothic world, what&rsquo;s not to like about it?</p> Nextcloud News https://bubelov.com/blog/2020/nextcloud-news/ Sat, 29 Aug 2020 00:00:00 +0000 https://bubelov.com/blog/2020/nextcloud-news/ <p>I&rsquo;ve been using Nextcloud for a while now, and I really enjoy it. It&rsquo;s an ambitious platform which can host and manage a lot of different services in a plug and play fashion. The core service of Nextcloud is file syncing. It can be used as an alternative to Dropbox or Google Drive, but it goes far beyond storing your files, and it doesn&rsquo;t expose your private data to all of those shady companies that don&rsquo;t deserve our trust.</p> <p>It&rsquo;s easy to extend Nextcloud by installing additional apps from its official app store. One of my favorite Nextcloud apps is <a href="https://apps.nextcloud.com/apps/news">News</a>, and it allows me to keep an auto-updating list of RSS feeds on my Nextcloud server which I can always access from an official Android app. Having a single place to store all of my news and podcasts alongside with their state (new/seen, starred/unstarred) allows me to read my news feed from any platform. Another benefit of having a server for my RSS feeds is that the changes I make via any of my client apps get propagated to all the other apps automatically.</p> <p>I tried to use the official Nextcloud News app for a few months, and it didn&rsquo;t align with my usage patterns very well. I&rsquo;ve been thinking about it for a few days, and I decided to solve this issue in a pretty radical way: by creating my own Nextcloud News Android app. I have no illusions about how much time it will take but reading RSS feeds is an important part of my daily routine, and I feel that it&rsquo;s worth the effort. I&rsquo;ll probably finish it by the next month, so I&rsquo;ll write more about this project in my later posts.</p> IP Forwarding With Nftables https://bubelov.com/blog/2020/ip-forwarding-nftables/ Sun, 23 Aug 2020 00:00:00 +0000 https://bubelov.com/blog/2020/ip-forwarding-nftables/ <p>I have a Digital Ocean droplet which I use as a proxy for my self-hosted home server. I&rsquo;ve been using some hacky solution based on <code>iptables</code> but it was a pain in the ass, so I&rsquo;ve decided to get rid of this piece of technical debt and the first thing that I noticed was the deprecation of <code>iptables</code> utility itself.</p> <p>It certainly looks like <code>nftables</code> is the future, so I played with it for a while, and I settled with the following config for my proxy:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/usr/sbin/nft -f </span></span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl">define <span class="nv">wan</span> <span class="o">=</span> eth0 </span></span><span class="line"><span class="cl">define <span class="nv">wg</span> <span class="o">=</span> wg0 </span></span><span class="line"><span class="cl">define <span class="nv">wg_net</span> <span class="o">=</span> 10.0.0.0/24 </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1"># TODO Since Linux kernel 5.2, there is support for performing stateful NAT in inet family chains.</span> </span></span><span class="line"><span class="cl">table ip wg <span class="o">{</span> </span></span><span class="line"><span class="cl"> chain prerouting <span class="o">{</span> </span></span><span class="line"><span class="cl"> <span class="c1"># Priority = NF_IP_PRI_NAT_DST (-100, destination NAT)</span> </span></span><span class="line"><span class="cl"> <span class="nb">type</span> nat hook prerouting priority -100<span class="p">;</span> </span></span><span class="line"><span class="cl"> iif <span class="nv">$wan</span> tcp dport <span class="o">{</span> http, https <span class="o">}</span> dnat 10.0.0.2 </span></span><span class="line"><span class="cl"> <span class="o">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> chain postrouting <span class="o">{</span> </span></span><span class="line"><span class="cl"> <span class="c1"># Priority = NF_IP_PRI_NAT_SRC (100, source NAT)</span> </span></span><span class="line"><span class="cl"> <span class="nb">type</span> nat hook postrouting priority 100<span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="c1"># Mask WireGuard packets as if they come from this server</span> </span></span><span class="line"><span class="cl"> oif <span class="nv">$wan</span> ip saddr <span class="nv">$wg_net</span> masquerade </span></span><span class="line"><span class="cl"> <span class="o">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> chain input <span class="o">{</span> </span></span><span class="line"><span class="cl"> <span class="nb">type</span> nat hook input priority 100<span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="o">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> chain output <span class="o">{</span> </span></span><span class="line"><span class="cl"> <span class="nb">type</span> nat hook output priority -100<span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="o">}</span> </span></span><span class="line"><span class="cl"><span class="o">}</span> </span></span></code></pre></div><p>It looks way cleaner than the <code>iptables</code> equivalent, and it can certainly be refactored to look even more readable. I really like its declarative way of describing the traffic rules.</p> <p>The <code>prerouting</code> chain just redirects all the TCP traffic coming to the ports 80 or 443 to my home-based Raspberry Pi 4. Both DO droplet and RPi share the same WireGuard network between them. The RPi IP address is 10.0.0.2, that&rsquo;s why dnat points there in the config above. I should probably convert this magic number into a variable, thankfully <code>nftables</code> supports that.</p> <p>The <code>postrouting</code> chain is pretty interesting. It allows me to use that DO droplet as a VPN server. It masks all the outgoing traffic with this server&rsquo;s IP address but that traffic can originate from any node in my WireGuard network. Such a VPN won&rsquo;t give you a lot of privacy since most of the mainstream hosting providers know your name, email and even your credit card info, but it can certainly be used to circumvent certain kinds of Internet censorship in your real location.</p> XCOM 2 https://bubelov.com/blog/2020/xcom-2/ Fri, 14 Aug 2020 00:00:00 +0000 https://bubelov.com/blog/2020/xcom-2/ <p>That&rsquo;s what you would see if you try to launch this game:</p> <p><figure> <a href="https://bubelov.com/blog/2020/xcom-2/xcom-2-shame_hu_1e9a73bb116954d.webp"> <img src="https://bubelov.com/blog/2020/xcom-2/xcom-2-shame_hu_78165ee5b9bee559.webp" alt="" /> </a> </figure></p> <p>Adorable, isn&rsquo;t it? Agree by default, but hey, you can disagree, right? You can, but 2K Games will keep showing you this nonsense after every launch. Probably those bastards have been forced by law to ask for your permission before doing their shady stuff and that pissed them off. They think it&rsquo;s their data, and they will keep annoying you with this blocking dialog until you surrender it. Modern games are so much fun.</p> <p>The game itself is pretty good, I like it. It&rsquo;s not as hardcore as Terror From the Deep, but I didn&rsquo;t expect it to be similar to its legendary ancestor which was made almost three decades ago. I expected something similar to XCOM: Enemy Unknown (2012) and I got what I expected. The old XCOM (1994-1995) and the new XCOM (2012+) are just different games. They don&rsquo;t have to be similar, and I&rsquo;m sure that their audience is pretty different too.</p> <p><figure> <a href="https://bubelov.com/blog/2020/xcom-2/xcom-2_hu_d5fbf30a603d293a.webp"> <img src="https://bubelov.com/blog/2020/xcom-2/xcom-2_hu_e78e1ade6ef63a21.webp" alt="" /> </a> </figure></p> <p>As you can see, this game isn&rsquo;t about good graphics. It&rsquo;s mostly about making the right tactical and strategic decisions. Good tactics can compensate for bad strategy and vice versa. It also does a good job at creating an emotional attachment between the players and their squads so any danger of losing one of your favorite soldiers can feel pretty terrifying. That&rsquo;s an interesting way to make the game much darker and scarier, and it&rsquo;s the unifying feature of all tactical XCOM games no matter new or old.</p> Software Engineering Interviews https://bubelov.com/blog/2020/se-interviews/ Mon, 10 Aug 2020 00:00:00 +0000 https://bubelov.com/blog/2020/se-interviews/ <p>I had an odd job this month which was in essence an attempt to find a good Android developer for a company I know very little about. It still amazes me that there is almost no correlation between what people write about in their CVs and what they really know.</p> <p>I&rsquo;m not a big fan or preparing for interviews, it sounds artificial, and it really is. Needless to say that it doesn&rsquo;t show anything useful. Most of the time, it&rsquo;s fine not to know something. We can&rsquo;t have an expert knowledge in every possible domain and no one should expect that from someone else. It seems pretty obvious, and that&rsquo;s what CVs help us to solve: people can see what we worked with, which technologies we explored, and it makes total sense to discuss it during the tech interview.</p> <p>It turns out, it&rsquo;s still OK to brag about your expertise in networks and know nothing about network timeouts or to be a math major and understand nothing about the basic math concepts. How can a &ldquo;Kotlin Expert&rdquo; with many years of experience believe that Kotlin has its own VM? People keep putting stuff they&rsquo;re unable to reason about in their CVs. It seems like by &ldquo;knowledge&rdquo; they mean knowledge of a basic and most typical setup. Anyone can use most of the modern tools and libraries, it doesn&rsquo;t show expertise, and it&rsquo;s not relevant.</p> <p>In my opinion, a real expert in a certain set of technologies should understand how they work and should be able to explain their choice. I&rsquo;m not sure if it&rsquo;s too much to ask nowadays.</p> Fairphone https://bubelov.com/blog/2020/fairphone/ Thu, 06 Aug 2020 00:00:00 +0000 https://bubelov.com/blog/2020/fairphone/ <p>There is a growing demand for privacy and many people started to acknowledge a problem with the duopoly of Google and Apple in the smartphone market. Smartphones in general are perfect privacy-invading devices and both Google and Apple put their own closed-source code into their phones, and they are pretty open about what this code does: it collects your personal data, every piece it can find.</p> <p>While you can&rsquo;t install an alternative OS on an iPhone, you can do it with your Android smartphone. LineageOS is a brave attempt to free the Android OS from the Google surveillance empire. I use it, and it works great but having a clean operating system doesn&rsquo;t solve the whole problem.</p> <p>The main issue with LineageOS is that you can&rsquo;t install it on any Android smartphone. Even worse, they won&rsquo;t even recommend you any particular smartphone. Choosing hardware for LineageOS is tough, and you shouldn&rsquo;t expect any serious guarantees on how long they will support your device of choice. It&rsquo;s not a critique of LineageOS. I use it, and I love it, but this model has some pretty obvious limitations.</p> <p>I believe that LineageOS is still relevant and important, but it&rsquo;s also important to explore some alternative ways to protect our data from malicious big tech companies, ISPs and other parties that are able to leverage the weaknesses and gray areas in our smartphones for profit.</p> <p>One of those ways is to focus on hardware. Having an OS that can run on many smartphones is great, but is&rsquo;s also important to have a smartphone that can last for years and that can run many operating systems. Here are three smartphone hardware projects I find interesting:</p> <ul> <li><a href="https://www.pine64.org/pinephone/">PinePhone</a></li> <li><a href="https://puri.sm/products/librem-5/">Purism Librem 5</a></li> <li><a href="https://www.fairphone.com/en/">Fairphone</a></li> </ul> <p>Purism hardware seem to be pretty expensive and also the least open. PinePhone is both open hardware and open software. It can run many Linux distributions and it can also run Android but full compatibility is not guaranteed if you choose that option.</p> <p>Fairphone seems to be a good option for people who aren&rsquo;t ready to move from Android to a Linux distro. Both Android and iOS have many polished apps and games, and it would be naive to think that mobile Linux distributions will be able to match all of that any time soon. I don&rsquo;t like the fact that Fairphone has Google apps and services by default. Of course, you can install LineageOS on your Fairphone, but it doesn&rsquo;t support it&rsquo;s newest model, at least officially.</p> <p>I&rsquo;m still not sure if Fairphone is better than any usual Android smartphone. It&rsquo;s expensive, and it does not really respect user privacy. Even their website has an annoying cookie dialog that won&rsquo;t let you use it unless you surrender your data. Yes, modular smartphones seem like a good idea, and I would really like to be able to use a smartphone for a decade or more and just buy some spare parts when something breaks. But with Fairphone prices, it feels like a rather expensive toy.</p> <p>I&rsquo;m not blind, and I see many problems with PinePhones, but I think that it&rsquo;s the most promising smartphone hardware project out there.</p> Don't Trust SQLite Timestamps https://bubelov.com/blog/2020/sqlite-timestamps/ Sat, 01 Aug 2020 00:00:00 +0000 https://bubelov.com/blog/2020/sqlite-timestamps/ <p>SQLite default timestamps produce a rather unusual output which is not standards-compliant and there are no easy ways to fix that. Let&rsquo;s say we have an SQLite database which uses the built-in <code>timestamp()</code> function to calculate the default values for a certain column:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">CREATE</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">guest_log</span><span class="w"> </span><span class="p">(</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="nb">INTEGER</span><span class="w"> </span><span class="k">PRIMARY</span><span class="w"> </span><span class="k">KEY</span><span class="w"> </span><span class="n">AUTOINCREMENT</span><span class="p">,</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">name</span><span class="w"> </span><span class="nb">TEXT</span><span class="w"> </span><span class="k">NOT</span><span class="w"> </span><span class="k">NULL</span><span class="p">,</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">time</span><span class="w"> </span><span class="nb">TEXT</span><span class="w"> </span><span class="k">NOT</span><span class="w"> </span><span class="k">NULL</span><span class="w"> </span><span class="k">DEFAULT</span><span class="w"> </span><span class="k">CURRENT_TIMESTAMP</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="p">);</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="n">guest_log</span><span class="w"> </span><span class="p">(</span><span class="n">name</span><span class="p">)</span><span class="w"> </span><span class="k">VALUES</span><span class="w"> </span><span class="p">(</span><span class="s1">&#39;Bogeyman&#39;</span><span class="p">);</span><span class="w"> </span></span></span></code></pre></div><p>Optionally, you can prettify your output by changing a few default options:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">.headers on </span></span><span class="line"><span class="cl">.mode column </span></span></code></pre></div><p>Let&rsquo;s inspect the data:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">SELECT</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">guest_log</span><span class="p">;</span><span class="w"> </span></span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">name time </span></span><span class="line"><span class="cl">---------- ------------------- </span></span><span class="line"><span class="cl">Bogeyman 2020-08-02 05:50:44 </span></span></code></pre></div><p>Looks fine, until we try to parse it with anything that expects a standard timestamp format. Here is an example in Kotlin:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">import</span> <span class="nn">java.time.LocalDateTime</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">fun</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">date</span> <span class="p">=</span> <span class="nc">LocalDateTime</span><span class="p">.</span><span class="n">parse</span><span class="p">(</span><span class="s2">&#34;2020-08-02 05:50:44&#34;</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">println</span><span class="p">(</span><span class="n">date</span><span class="p">)</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>Oops, we&rsquo;ve got an exception:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Exception in thread &#34;main&#34; java.time.format.DateTimeParseException: </span></span><span class="line"><span class="cl">Text &#39;2020-08-02 05:50:44&#39; could not be parsed at index 10 </span></span></code></pre></div><p>Index 10? That&rsquo;s a space character. Let&rsquo;s check the documentation for the <code>parse</code> function:</p> <blockquote> <p>Obtains an instance of LocalDateTime from a text string such as 2007-12-03T10:15:30. ― <a href="https://sqlite.org/lang_datefunc.html">Java 11 documentation</a></p> </blockquote> <p>It looks like the text string this method expects should consist of:</p> <ul> <li>The <code>ISO_LOCAL_DATE</code></li> <li>The letter <code>T</code> (or <code>t</code>, parser is case-insensitive)</li> <li>The <code>ISO_LOCAL_TIME</code></li> </ul> <p>In fact, the only date format <code>LocalDateTime</code> supports is <code>ISO 8601</code>. Let&rsquo;s see if this standard allows spaces:</p> <blockquote> <p>A single point in time can be represented by concatenating a complete date expression, the letter &lsquo;T&rsquo; as a delimiter, and a valid time expression. For example, &lsquo;2007-04-05T14:30&rsquo;. It is permitted to omit the &lsquo;T&rsquo; character by mutual agreement as in &lsquo;200704051430&rsquo;. Separating date and time parts with other characters such as space is not allowed in ISO 8601. ― <a href="https://en.wikipedia.org/wiki/ISO_8601">Wikipedia</a></p> </blockquote> <p>There is no doubt that <code>ISO 8601</code> doesn&rsquo;t allow spaces in date strings and Java date parser rightfully fails when it stumbles upon illegal characters. That&rsquo;s odd, I thought SQLite uses the standard date and time formats. Here is what SQLite documentation says about that:</p> <blockquote> <p>The date and time functions use a subset of IS0-8601 date and time formats. ― <a href="https://sqlite.org/lang_datefunc.html">SQLite documentation</a></p> </blockquote> <p>That&rsquo;s very confusing. It seems like Java interpretation is correct and SQLite breaks <code>IS0 8601</code> by using spaces instead of <code>T</code>. Well, they won&rsquo;t fix this thing any time soon, that&rsquo;s for sure. So, what can we do about that?</p> <p>Let&rsquo;s reproduce our problem:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">select</span><span class="w"> </span><span class="n">datetime</span><span class="p">();</span><span class="w"> </span></span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">datetime() </span></span><span class="line"><span class="cl">------------------- </span></span><span class="line"><span class="cl">2020-08-02 06:38:06 </span></span></code></pre></div><p>So, how can we generate a valid date with SQLite? There are two obvious options:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">SELECT</span><span class="w"> </span><span class="n">strftime</span><span class="p">(</span><span class="s1">&#39;%Y-%m-%dT%H:%M:%SZ&#39;</span><span class="p">),</span><span class="w"> </span><span class="k">replace</span><span class="p">(</span><span class="n">datetime</span><span class="p">(),</span><span class="w"> </span><span class="s1">&#39; &#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;T&#39;</span><span class="p">);</span><span class="w"> </span></span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">strftime(&#39;%Y-%m-%dT%H:%M:%SZ&#39;) replace(datetime(), &#39; &#39;, &#39;T&#39;) </span></span><span class="line"><span class="cl">----------------------------- ----------------------------- </span></span><span class="line"><span class="cl">2020-08-02T06:38:06Z 2020-08-02T06:38:06 </span></span></code></pre></div><p>The <code>replace()</code> option is less heavy-handed because it doesn&rsquo;t override default date format. It just adds the missing <code>T</code>, but there is no way to add a time zone designator without adding more functions which would make things even more hacky.</p> <p>Adding <code>Z</code> to the end of a date string is pretty important if we want our dates to be parsed correctly. Missing <code>Z</code> instructs timezone-aware parsers to assume local timezone instead of UTC. The output of date functions in SQLite always assumes UTC by default so it&rsquo;s generally a good idea to append <code>Z</code> to all date stings generated by SQLite.</p> <p>Note that <code>LocalDateTime</code> is not timezone-aware, so you should use <code>ZonedDateTime</code> or <code>OffsetDateTime</code> if you want to support <code>ISO 8601</code> timezones.</p> <p>So, let&rsquo;s try put it all together:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">CREATE</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">guest_log</span><span class="w"> </span><span class="p">(</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="nb">INTEGER</span><span class="w"> </span><span class="k">PRIMARY</span><span class="w"> </span><span class="k">KEY</span><span class="w"> </span><span class="n">AUTOINCREMENT</span><span class="p">,</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">name</span><span class="w"> </span><span class="nb">TEXT</span><span class="w"> </span><span class="k">NOT</span><span class="w"> </span><span class="k">NULL</span><span class="p">,</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">time</span><span class="w"> </span><span class="nb">TEXT</span><span class="w"> </span><span class="k">NOT</span><span class="w"> </span><span class="k">NULL</span><span class="w"> </span><span class="k">DEFAULT</span><span class="w"> </span><span class="p">(</span><span class="w"> </span><span class="n">strftime</span><span class="p">(</span><span class="s1">&#39;%Y-%m-%dT%H:%M:%SZ&#39;</span><span class="p">)</span><span class="w"> </span><span class="p">)</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="p">);</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="n">guest_log</span><span class="w"> </span><span class="p">(</span><span class="n">name</span><span class="p">)</span><span class="w"> </span><span class="k">VALUES</span><span class="w"> </span><span class="p">(</span><span class="s1">&#39;Bogeyman&#39;</span><span class="p">);</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="k">SELECT</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">guest_log</span><span class="p">;</span><span class="w"> </span></span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">id name time </span></span><span class="line"><span class="cl">-- -------- -------------------- </span></span><span class="line"><span class="cl">1 Bogeyman 2022-04-28T05:08:52Z </span></span></code></pre></div><p>A bit hacky, isn&rsquo;t it? Databases are fun.</p> <h2 id="conclusion">Conclusion</h2> <p>Databases are full of quirks and this particular problem is just a tip of the iceberg. Moving some business logic into a database might seem like a good idea but in many cases it&rsquo;s not. It&rsquo;s hard to avoid certain friction between your database and your code, which partly justifies the idea of shrinking the contact surface by keeping your databases as &ldquo;dumb&rdquo; as possible.</p> <p>Another argument for &ldquo;dumb&rdquo; databases is rather pragmatic. Most developers don&rsquo;t know their databases as well as their programming languages of choice. Offloading some business logic into a database shrinks the number of people who could understand the whole system and make informed decisions when it comes to the system development and maintenance.</p> <p>That said, I really like to push default values and data validation to the database layer, and I often do it in my own personal projects. There is nothing wrong with it, if you know what you&rsquo;re doing.</p> Days Gone https://bubelov.com/blog/2020/days-gone/ Tue, 28 Jul 2020 00:00:00 +0000 https://bubelov.com/blog/2020/days-gone/ <p><figure> <a href="https://bubelov.com/blog/2020/days-gone/days-gone_hu_9a66af7a7f03eccc.webp"> <img src="https://bubelov.com/blog/2020/days-gone/days-gone_hu_68b96431e75439da.webp" alt="" /> </a> </figure></p> <p>Many modern games are similar to movies, and I don&rsquo;t mean more realistic graphics. What I mean is the lack of agency: it&rsquo;s like a player becomes an interruption between dozens of long and glorious cut scenes.</p> <p>Days Gone is one of those games. I&rsquo;m not even sure is it&rsquo;s a good thing or a bad thing. It feels good, it&rsquo;s mostly fun to play and it can be challenging at times. The story is pretty corny but the graphics is good. This game felt like Horizon: Zero Dawn, except for the fact that the story line lacks the depth and quality of the Horizon franchise. Don&rsquo;t get me wrong, Days Gone is a good time killer. It just lacks novelty, that&rsquo;s all.</p> Bitcoin Price Movements https://bubelov.com/blog/2020/bitcoin-price-movements/ Thu, 23 Jul 2020 00:00:00 +0000 https://bubelov.com/blog/2020/bitcoin-price-movements/ <p>Everyone knows about Bitcoin but no one cares about it except once every few years, when it suddenly jumps in price and people start to go crazy. Some people call this pattern a hype cycle. There is no shortage of crazy-ass traders who try to profit during these short-lived periods of perceived irrationality.</p> <p>Bitcoin price have become more volatile in the past few days which doesn&rsquo;t really say anything for sure, but it gives many people hope that we&rsquo;ll see some price action in the near future. Does it make sense to buy Bitcoin in the anticipation of a new price rally? It might, but it&rsquo;s still a huge risk.</p> <p>What makes more sense is to use any rapid increase in Bitcoin price to slowly diversify your portfolio. If you have 15% of your portfolio in Bitcoin and one day you realize that this proportion had increased dramatically due to a sudden Bitcoin price spike, there is nothing wrong with reacting promptly and cutting it back to 15% target. It&rsquo;s easier said than done, but it&rsquo;s extremely important not to become too greedy and reckless in those crazy times.</p> <p>Bitcoin bear markets are tough, and they can last for many years. Make sure you can handle sharp and prolonged price declines. Here are my thoughts on <a href="https://bubelov.com/blog/2018/crypto-as-asset-class/">Bitcoin as an asset class</a> and here are more details on <a href="https://bubelov.com/blog/2020/plotting-financial-data-in-python/">the benefits of diversification</a>.</p> Rise of the Tomb Raider https://bubelov.com/blog/2020/rise-of-tomb-raider/ Tue, 14 Jul 2020 00:00:00 +0000 https://bubelov.com/blog/2020/rise-of-tomb-raider/ <p><figure> <a href="https://bubelov.com/blog/2020/rise-of-tomb-raider/tomb-raider_hu_8ebc87b85900bc5d.webp"> <img src="https://bubelov.com/blog/2020/rise-of-tomb-raider/tomb-raider_hu_5093ca98751634.webp" alt="" /> </a> </figure></p> <p>I never played a Tomb Raider game before because I don&rsquo;t really like action games. Of course, AAA game developers are aware of people like me so they started to add RPG and open world elements to every fucking game. Well, it works. I mean, I wouldn&rsquo;t even think about buying this game, but it was available on PS Plus for free, and I was curious about the Tomb Raider series.</p> <p>It&rsquo;s a good-looking and well crafted action game, but I didn&rsquo;t notice anything exceptional about it. It feels like just another Hollywood movie: it looks pleasing while you watch it but in the end it becomes clear that it&rsquo;s won&rsquo;t leave a trace in your memory or in the popular culture in general.</p> Nextcloud and SQLite https://bubelov.com/blog/2020/nextcloud-sqlite/ Mon, 06 Jul 2020 00:00:00 +0000 https://bubelov.com/blog/2020/nextcloud-sqlite/ <p>I have my own Nextcloud instance which uses PostgreSQL to store its data. I have nothing against PostgreSQL, but it seems to be an overkill for a single-server deployment used by a single user. Well, it&rsquo;s used occasionally by other people but the number of concurrent users is always low.</p> <p>Nextcloud documentation states that it doesn&rsquo;t care much about your choice of a database, and you&rsquo;re free to use SQLite. In fact. SQLite is a default data backend for a Nextcloud instances. The problem is: the documentation also advises against using SQLite for anything but minimal deployments, but it&rsquo;s hard to say what it means by minimal. It might be concerned about scalability, which is not really an issue in my case, but it could also mean correctness or stability. I guess the only way to find out if it&rsquo;s OK to use SQLite is to try it out.</p> The Cloudflare Hype: Not Impressed https://bubelov.com/blog/2020/cloudflare/ Thu, 02 Jul 2020 00:00:00 +0000 https://bubelov.com/blog/2020/cloudflare/ <p>It seems like everyone is talking about Cloudflare, and I know plenty of developers who use it in their projects. I couldn&rsquo;t resist the hype, so I decided to try it out for myself.</p> <p>I have to admit, I&rsquo;m not impressed. For a relatively simple setup, it mostly adds complexity. It feels like you&rsquo;re signing up for a gateway drug: once you&rsquo;re in, you&rsquo;re constantly nudged toward buying &ldquo;optimizations&rdquo; like their image optimizer, which strikes me as a clumsy, band-aid solution to a problem that should be handled at the application or build-process level, not by a middleman CDN.</p> <p>The core service works, but the constant upsell and added layers of abstraction make me question whether the trade-off is worth it for anything but the most massive, attack-prone sites.</p> Detroit: Become Human https://bubelov.com/blog/2020/detroit-become-human/ Wed, 01 Jul 2020 00:00:00 +0000 https://bubelov.com/blog/2020/detroit-become-human/ <p>That&rsquo;s a special one, really. This game has a high quality non-linear story which is already enough to stand out from the crowd. A single mistake can have enormous consequences, and you won&rsquo;t see it until it&rsquo;s too late to go through the familiar save-fail-load-repeat cycle.</p> <p>Detroit: Become Human creates a good sense of presence, and you just can&rsquo;t feel indifferent as the story progresses. It&rsquo;s hard to escape a movie analogy here because, in my opinion, you need a big screen and a dark room in order to enjoy this game the most.</p> Migrating to Vanilla CSS https://bubelov.com/blog/2020/vanilla-css/ Fri, 26 Jun 2020 00:00:00 +0000 https://bubelov.com/blog/2020/vanilla-css/ <p>I don&rsquo;t make web front-ends often and, honestly, I would prefer not to touch them at all. That said, I still have this website as well as a few blogs and landing pages to support, so I don&rsquo;t have much of a choice. I have no time and no interest in trying dozens of different CSS frameworks in search for the Holy Grail, so I&rsquo;ve been using the most mainstream solution for a while. Of course, I&rsquo;m talking about Bootstrap.</p> <p>Bootstrap is cool. It&rsquo;s customizable, it&rsquo;s not too verbose, and it just works (in most of the cases). The only thing that annoys me is the fact that it needs crazy amounts of JavaScript for no good reason, and I&rsquo;m not a big fan of bloated webpages. Over-sized webpages is a huge problem, and I don&rsquo;t want to be a part of it.</p> <p>The Bootstrap contributors understand those complaints, and they cut down on JavaScript in of Bootstrap 5, which is still in development. First, I wanted to switch to Bootstrap 5, but then I thought &ldquo;why don&rsquo;t I try vanilla CSS?&rdquo;. One weekend later, it turned out that it&rsquo;s pretty easy to use vanilla CSS, and it actually made it easier for me to experiment with website styling. There is some comfort in not having to manage NPM dependencies, and it&rsquo;s a pleasure to build directly on the well thought standards that are aimed to last for decades. Software tools are so ephemeral nowadays, maybe it&rsquo;s better to cut down on navel-gazing a bit, at least in the area I don&rsquo;t really like.</p> Goodbye, Joda-Time https://bubelov.com/blog/2020/goodbye-joda-time/ Mon, 22 Jun 2020 00:00:00 +0000 https://bubelov.com/blog/2020/goodbye-joda-time/ <p>The first language I used professionally is Java 6. Even at that time it was already considered old and obsolete. Some of its native APIs are a pure horror, and that&rsquo;s why the open source community created a lot of libraries that make Java 6 developer&rsquo;s life a bit more pleasant. Joda-Time is still a great success, mostly because of a total failure which is a time API used in Java 6 and 7.</p> <p>Old habits die hard, I guess. So, in June 2020 I finally decided to check if there are better tools than Joda-Time. Why change the things that work? Well, at least it would allow me to cut one more external dependency, which is already a good move in my book.</p> <p>After doing some research, I ended up switching to Java 8 time API. It looks like it borrowed heavily from Joda-Time, which is pretty nice. Nothing to add here, really. It just works. The only difference is that my dependencies list had shrunk by one line. What a great use of human effort.</p> Chicago Boys https://bubelov.com/blog/2020/chicago-boys/ Mon, 15 Jun 2020 00:00:00 +0000 https://bubelov.com/blog/2020/chicago-boys/ <p>Chicago Boys is a controversial documentary that tackles some very tough, deep questions. It shows how economic policies can lift countries out of poverty. Economic reforms are hard and painful, even when they lead to better outcomes for most people, some inevitably get hurt in the process.</p> <p>That&rsquo;s what happened in my home country in the 1990s, and it&rsquo;s also what happened in Chile in the 1970s and &rsquo;80s under Augusto Pinochet. His policies transformed Chile from a struggling nation into a leading Latin American economy. But it&rsquo;s crucial to discuss the price of that economic transformation.</p> <p>The price was at least 3,000 lives. Augusto Pinochet&rsquo;s regime killed or &ldquo;disappeared&rdquo; thousands and destroyed a functioning democracy. Was it the right decision?</p> <p>Some defenders of the coup argue it was a necessary, preemptive strike to save Chile from a communist dictatorship. However, most historians reject this &ldquo;lesser evil&rdquo; justification. The scale of Pinochet&rsquo;s documented, systematic violence so vastly exceeded the political conflict of the preceding years that it stands not as a necessary evil, but as a catastrophic rupture of Chile&rsquo;s democratic life.</p> <p>Of course, I don&rsquo;t know all the details, but after watching the film I&rsquo;m left convinced those deaths were completely unnecessary. The violence looks more like an emotional purge than a rational political calculation.</p> <p>Destroying a sick democracy? Perhaps, there may be no point in preserving a system that has truly failed its people. But killing thousands for no clear, defensible reason? That isn&rsquo;t reform, it&rsquo;s a crime.</p> <p>History shows that sometimes a group of people is suppressed by any means necessary because its ideology is seen as an existential threat. People in power can feel forced by their convictions to take this gamble. The DEA believes drug traffickers are evil and almost everyone agrees terrorists are evil. We often identify groups we deem so malicious that we consider their elimination a necessary, preventive act.</p> <p>In the case of Chile, this logic simply doesn&rsquo;t fit. The left-wing radicals were relatively toothless and lacked broad popular support. What happened looks less like a necessary, targeted suppression and more like the consequence of paranoia fused with absolute, centralized power.</p> Disco Elysium https://bubelov.com/blog/2020/disco-elysium/ Sun, 07 Jun 2020 00:00:00 +0000 https://bubelov.com/blog/2020/disco-elysium/ <p>Honestly, I don&rsquo;t know why I still play video-games. For people of my age, it&rsquo;s more orthodox to satisfy all the digital entertainment needs by using Netflix and Spotify. I have both, but I don&rsquo;t use them often. Yea, I&rsquo;m a pretty boring workaholic.</p> <p>That might be true, but I would argue that modern music, movies and video-games are also pretty boring. Once you consume enough of each kind of entertainment, it all starts to look like an endless stream of deeply familiar patterns. I didn&rsquo;t have high expectations of Disco Elysium, buy this game wildly exceeded my expectations.</p> <p>The thing which sold it to me was an open world PRG label. It&rsquo;s easy. See an open world RPG? Buy it. It works for me every time. Well, almost every time. I was expecting something like Diablo (I don&rsquo;t know why, because of isometric view, maybe?) but I was faced with something closer to a quest game. This game is not based on fighting, it&rsquo;s based on dialogs and the real beauty of Disco Elysium is the topics you have to discuss with different NPCs populating the game world. Expecting fights? Here is some dark humor and philosophical ramblings instead. Unexpected. Wow, that&rsquo;s something new. I rarely finish main stories in games, and I must say, I really enjoyed every minute of Disco Elysium, and there are many minutes in 35 hours.</p> WireGuard https://bubelov.com/blog/2020/wireguard/ Sat, 30 May 2020 00:00:00 +0000 https://bubelov.com/blog/2020/wireguard/ <p>I have a Nextcloud server in my house, and it works great inside a local network, but it also needs to be reachable from the Internet and that&rsquo;s where it gets a bit tricky. My server has a local IP, but it doesn&rsquo;t have a globally reachable IP, and there are no easy ways to get one from a regular residential ISP.</p> <p>The most convenient way to reach a website is by its domain name. Any browser can execute a DNS query to figure out the actual IP addresses behind a particular domain name you&rsquo;re trying to access and once that address are found, your browser of choice can finally initiate a connection.</p> <p>So, my Nextcloud server needs the following things to be globally reachable:</p> <ol> <li>A domain.</li> <li>A unique IP address to point that domain to.</li> </ol> <p>I already had a domain and the only thing that missed was an unique IP address to put inside an <a href="https://en.wikipedia.org/wiki/List_of_DNS_record_types">A record</a>. It turned out, Digital Ocean has a data center close enough (ping &lt; 30 ms) to my house and I&rsquo;ve decided to rent a cheap virtual machine there and use it&rsquo;s globally reachable IP address for my local Nextcloud server.</p> <p>It&rsquo;s fair to stop there and ask yourself &ldquo;why didn&rsquo;t this guy just host his stupid Nextcloud on that droplet directly? What&rsquo;s the point of hosting stuff from home?&rdquo;. Those are good questions and the answer is: privacy and financial costs. I don&rsquo;t want to hold my private data anywhere except my house, and I also don&rsquo;t want to pay a lot of money for storage space that I already have and can use for free. Even buying a new HDD or an SSD is much, much cheaper than renting the equivalent space in the cloud.</p> <p>So, my domain&rsquo;s A record can point all the traffic to that DO droplet which has a unique and globally reachable IP address so the only extra thing I needed to do is to connect my home server with that VM somehow. I heard that setting up OpenVPN is not particularly easy, and I wanted to find an easier solution. A few web searches later, I ended up reading about a tool called <code>autossh</code>.</p> <p><a href="https://blog.wirelessmoves.com/2019/06/how-to-run-a-server-at-home-without-an-ipv4-address.html">This excellent post</a> helped me to get it up and running in no time but, unfortunately, <code>autossh</code> is kind of cryptic and sometimes it just stops working without logging any reasons why. That&rsquo;s why I had to get rid of <code>autossh</code> and find a way to set up a proper VPN.</p> <p>It turned out, setting up VPN can be pretty easy, thanks to <a href="https://www.wireguard.com">WireGuard</a>. It&rsquo;s also the <a href="https://www.wireguard.com/performance">fastest</a> out there. I highly recommend using WireGuard if you need a fast and reliable connection between your home computer and some server in the Internet.</p> RetroPie https://bubelov.com/blog/2020/retropie/ Wed, 27 May 2020 00:00:00 +0000 https://bubelov.com/blog/2020/retropie/ <p>I was a passionate gamer during my teenage years, and I still play games from time to time. Sometimes I remember some random game from my childhood, and I immediately want to play it, but it can be pretty hard for the following reasons:</p> <ol> <li>It&rsquo;s not always easy to find and install an emulator.</li> <li>Console games don&rsquo;t work very well with keyboard and a mouse, and I don&rsquo;t even have an always available mouse.</li> </ol> <p>Game console manufacturers started to recognize all that nostalgic demand, and now it&rsquo;s possible to buy a retro console from a company such as Nintendo, and it will arrive bundled with a few cool games from the past. The problem is: you have to buy a retro version of every console that you want to play and those consoles might be ancient, but they aren&rsquo;t cheap. Personally, I just don&rsquo;t want more electronics nearby my TV and I don&rsquo;t like the friction of switching between different game consoles.</p> <p>It turns out, it&rsquo;s easy to convert your Raspberry Pi into a meta-console that can emulate dozens of retro consoles, including NES, Sega Mega Drive, Sony PlayStation and Sega Dreamcast. You can grab an SD card, download an image from the <a href="https://retropie.org.uk/">RetroPie website</a> and you&rsquo;re good to go. It also understands many modern game controllers, and I had no issues using it with my PS4 DualShock gear.</p> Why Apps Need Cache https://bubelov.com/blog/2020/why-apps-need-cache/ Mon, 25 May 2020 00:00:00 +0000 https://bubelov.com/blog/2020/why-apps-need-cache/ <p>It&rsquo;s hard to find an application which don&rsquo;t need to remember your previous actions and choices. People don&rsquo;t like to lose their progress, preferences or to be forced to sign in every time they open an app.</p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#introduction">Introduction</a></li> <li><a href="#stateless-apps">Stateless Apps</a></li> <li><a href="#webview">WebView</a></li> <li><a href="#basic-types-of-data">Basic Types of Data</a></li> <li><a href="#sharedpreferences">SharedPreferences</a></li> <li><a href="#sql-and-sqlite">SQL and SQLite</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="introduction">Introduction</h2> <p>It&rsquo;s not surprising that the most frequently saved piece of persistent data is auth credentials because asking your customers to authorize too often is a sure way to lose those customers. This example is rather extreme, but it clearly shows how a sensible caching policy can make our applications more user-friendly.</p> <p>Since saving state is such an important thing, it&rsquo;s tempting to assume that there must be a standard way of doing that. Well, there is no golden standard but there are a few widely adopted approaches.</p> <h2 id="stateless-apps">Stateless Apps</h2> <p>OK, that &ldquo;sign in every time&rdquo; example is hard to argue with but why would we save any data except for auth credentials? Let&rsquo;s say we have a native app and a bunch of REST API endpoints that provide all the data we need. We can simply call them every time a user opens an app and rest assured that the user is always presented with the latest data.</p> <p>There are three main problems with this approach:</p> <ol> <li> <p><strong>Reliability</strong>. Let&rsquo;s say we need to fetch a list of ATMs in a particular town. In the real world, network requests take an unpredictable amount of time, and they might also fail, so you could easily end up with no data to show. Even if your request succeeds after all, it&rsquo;s worth noting that people don&rsquo;t like to be blocked by progress indicators, especially if the waiting time is unpredictable.</p> </li> <li> <p><strong>Latency</strong>. Fetching data from a remote server will always be slower than fetching it from a local cache. Blame the speed of light: this nasty phenomenon puts a strict limit on how fast the data can move from one point to another and time, indeed, is a precious commodity.</p> </li> <li> <p><strong>Redundancy</strong>. To continue our ATM example: it would be nice to be able to fetch only the ATMs that were added or updated since the last call. Those things tend to be pretty static, and it&rsquo;s also true for many other use cases. Such an approach can reduce server workload and speed up the app, but it also means that we need to have a cache, and we also need to be able to manage its ever-growing complexity.</p> </li> </ol> <p>So, not having a cache is not the end of the world but let&rsquo;s not pretend that it&rsquo;s right and that the users wont notice such a shoddy job.</p> <h2 id="webview">WebView</h2> <p>Hey, can&rsquo;t we take an existing website and wrap it into a <a href="https://developer.android.com/reference/android/webkit/WebView">WebView</a>? Such an &ldquo;app&rdquo; can use request caching and fetch all the caching instructions from our servers. That may seem pretty convenient but is it a good solution?</p> <p>The main drawback of non-native UIs is poor performance: no one likes to see a slow and blurry interface on their new and shiny octa-core smartphone. High quality apps need native user interfaces and that means we can&rsquo;t get away with simply loading an existing website inside a WebView and calling it a day. This practice is not banned (yet?) but those apps are garbage and the users know that.</p> <h2 id="basic-types-of-data">Basic Types of Data</h2> <p>Where can we put our cache? First, let&rsquo;s split the data we may receive in two categories, just for convenience’s sake:</p> <ol> <li>Binary data such as images, songs, videos, etc</li> <li>Textual data of certain structure that represents domain objects</li> </ol> <p>We rarely need to mess around with images, so it&rsquo;s almost always better to use an image handling library such as <a href="https://square.github.io/picasso/">Picasso</a>. This library can cache your images automatically, so you can have one less thing to worry about. When it comes to other kinds of binary data, it&rsquo;s generally a good idea to store it somewhere outside your database.</p> <p>Structured data that we may generate locally or fetch from the remote API is a different beast and that&rsquo;s where databases shine.</p> <h2 id="sharedpreferences">SharedPreferences</h2> <p>The state of any app is just a bunch of data structures, and it can be (grossly inefficiently) expressed as a single <a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-string/">String</a>. The simplest way to persist strings on Android is to use <a href="https://developer.android.com/reference/android/content/SharedPreferences">SharedPreferences API</a> which gives us a simple way to save the app state.</p> <p>Is it a good solution? Let&rsquo;s consult with the official documentation:</p> <blockquote> <p>Frequently changing properties or properties where loss can be tolerated should use other mechanisms. ― <a href="https://developer.android.com/reference/android/content/SharedPreferences">SharedPreferences documentation</a></p> </blockquote> <p>Looks like a reminder that you shouldn&rsquo;t abuse SharedPreferences by putting large amounts of non-essential data into it. SharedPreferences is not a memory cache, after all, although it acts in a similar way. Also, don&rsquo;t forget to use <a href="https://developer.android.com/reference/android/content/SharedPreferences.Editor#apply()"><code>apply()</code></a> instead of <a href="https://developer.android.com/reference/android/content/SharedPreferences.Editor#commit()"><code>commit()</code></a> when putting data there.</p> <p>Let&rsquo;s look at another excerpt:</p> <blockquote> <p>Preferences: Store private, primitive data in key-value pairs. ― <a href="https://developer.android.com/training/data-storage">Data storage documentation</a></p> </blockquote> <p>Primitive data structures is the ideal use case for SharedPreferences. For instance, it&rsquo;s a good place to put your auth token, because it&rsquo;s usually just a short string, and it&rsquo;s not directly related to the rest of your data.</p> <blockquote> <p>Databases: Store structured data in a private database using the Room persistence library. ― <a href="https://developer.android.com/training/data-storage">Data storage documentation</a></p> </blockquote> <p>The recommendation is clear: structured data should be stored in a database. Is it a sane recommendation? Of course it is, it&rsquo;s very hard to manage data when it lacks structure or when it&rsquo;s structure is unconventional.</p> <p>So, what about Room? Well, it&rsquo;s not a bad tool for state persistence, but I think that we have a better option: SQLDelight. But first, let&rsquo;s talk about SQL.</p> <h2 id="sql-and-sqlite">SQL and SQLite</h2> <p>SQL stands for Structured Query Language and it&rsquo;s used pretty much everywhere, including Android. In fact, it powers most of the server databases. SQL is a simple language that allows us to interact with relational databases such as <a href="https://www.postgresql.org/">PostgreSQL</a> and most of those database engines are fine-tuned for high load multi-user environments, and they require skilled system administrators in order to run smoothly.</p> <p>So, SQL wasn&rsquo;t meant for mobile and embedded systems, initially. It all changed about 20 years ago with the release of <a href="https://sqlite.org/index.html">SQLite</a> - a simple database engine that understands SQL and stores each database in a single file. In Android, you don&rsquo;t have to do any set up and maintenance if you wish to use SQL. The Android platform provides you with a ready to use interface so utilizing the full power of SQL for your Android apps is very easy.</p> <p>The only problem with Android&rsquo;s SQLite API is the fact that it&rsquo;s very verbose, which means that you have to write a lot of boilerplate code in order to use SQL in your projects. That&rsquo;s why there are many higher level persistence libraries such as SQLDelight and Room.</p> <h2 id="conclusion">Conclusion</h2> <p>It&rsquo;s important to cache as much data as possible and there are many ways to do that. Using SharedPreferences is fine for a small amount of loosely structured data but SQL is the way to go if your state is more complex than a few primitive values. SQLite makes your state more transparent and easier to extract, and it also makes your data handling code more readable for people who are less familiar with Android-specific APIs. SQLDelight and Room are great tool for state management because they decouple Android-specific logic from data queries and eliminate the need to write a lot of boilerplate code.</p> Dark Stone https://bubelov.com/blog/2020/dark-stone/ Wed, 20 May 2020 00:00:00 +0000 https://bubelov.com/blog/2020/dark-stone/ <p>I spent countless hours playing this game during my childhood. Some people say it&rsquo;s a Diablo clone, and this game has a crappy rating, but I absolutely love it. This was the first game which I tried on a RetroPie and I finished it in one evening (on an easy difficulty, of course). I&rsquo;d say it aged well, and it still feels great. Highly recommend.</p> Ubuntu 20.04 + Raspberry Pi https://bubelov.com/blog/2020/ubuntu-20-04-rpi/ Mon, 18 May 2020 00:00:00 +0000 https://bubelov.com/blog/2020/ubuntu-20-04-rpi/ <p>There are many things to like about Raspberry Pi, but its official operating system in not one of those things. It uses an ancient kernel, it lacks many popular packages, and it&rsquo;s 32 bit, which is a shame. Personally, I also don&rsquo;t like when hardware manufacturers try to mess with the software, because I believe in division of labor.</p> <p>That&rsquo;s why I&rsquo;m very excited about the fact that Ubuntu have decided to officially support Raspberry Pi in their new and cool 20.04 LTS distribution. I tried it with a couple of Raspberries, and it works like a charm. It has a 5.4 kernel with some back-ported WireGuard code, so it also makes it easier to set up a fast and modern VPN on a Raspberry Pi. In contrast, setting up WireGuard on Raspbian is a bit hacky, to say the least.</p> Computer Science of Crypto-Currency https://bubelov.com/blog/2020/crypto-cs/ Sat, 16 May 2020 00:00:00 +0000 https://bubelov.com/blog/2020/crypto-cs/ <p>I found an interesting <a href="https://bitstein.org/blog/nick-szabo-the-computer-science-of-crypto-currency/">set of links</a> that has a lot of good content that I believe can be valuable to people who want to understand some technicalities of Bitcoin (on a reasonably high level).</p> Gtypist https://bubelov.com/blog/2020/gtypist/ Wed, 13 May 2020 00:00:00 +0000 https://bubelov.com/blog/2020/gtypist/ <p>I learned to touch type pretty late but hey, better late than never. There are plenty of typing tutoring software online and most of it either full of ads or it will try to sell you a subscription. I mean, there is nothing wrong with asking users to open their wallets and that&rsquo;s how I actually learned to touch type. I used a paid service for a few months and I though that it&rsquo;s pretty decent.</p> <p>A couple of weeks ago, I&rsquo;ve decided to improve my typing speed, so I started looking for some service again. Luckily, I first tried to search the <code>apt</code> repository before using a search engine and that&rsquo;s how I found <a href="https://www.gnu.org/software/gtypist/">gtypist</a>.</p> <p>It turned out, that paid service that I used is crap. Well, at least compared to this free and beautiful tool. When I use <code>gtypist</code> I just feel that my skills improve a lot faster than when I use other tools. The fact that it doesn&rsquo;t require an Internet connection is also pretty cool. I have a weird habit of practicing touch typing during my travel so the offline mode of operation is a very handy thing to have.</p> Khan Academy https://bubelov.com/blog/2020/khan-academy/ Sat, 09 May 2020 00:00:00 +0000 https://bubelov.com/blog/2020/khan-academy/ <p>Some people think that programmers are smart and good at math. Nothing can be further from the truth, but it&rsquo;s never late to learn. Honestly, I don&rsquo;t think that an average front-end developer needs to know much about math or even algorithms or the networking protocols. It never hurts to know that, but it isn&rsquo;t what most programmers do. Stack Overflow is our secret weapon, and we use it to solve 99% of the issues (and introduce a few more).</p> <p>Math is kind of irrelevant for programmers. If you want to excel at job interviews, you better spend your time trying to memorize a bunch of algorithms and a Big O notation. Chances are, it won&rsquo;t make you a better programmer, but there is a huge difference between being good at getting programming jobs and being good at programming so if it&rsquo;s the first thing that you&rsquo;re after, it&rsquo;s clear that you shouldn&rsquo;t worry about math.</p> <p>That said, I believe that math skills can be beneficial to anyone and if you want to learn math or refresh your knowledge, <a href="https://www.khanacademy.org/">Khan Academy</a> is absolutely the best place to do that. I try to use it daily, but sometimes I take a break for a few months.</p> Jeffrey Epstein: Filthy Rich https://bubelov.com/blog/2020/filthy-rich/ Sat, 02 May 2020 00:00:00 +0000 https://bubelov.com/blog/2020/filthy-rich/ <p>Netflix knows how to retain its subscribers. Every time I start thinking about canceling my subscription or trying a different service, they release a bunch of great new movies that get me hooked and create a fear of missing out on something interesting if I stop using Netflix.</p> <p>I&rsquo;m a big fan of documentaries and an investigative journalism, and I must say, this new movie about Jeffrey Epstein is really great. I didn&rsquo;t follow his story closely, so I didn&rsquo;t know what a terrible guy he actually was and how much power he had. I heard that some nasty people give Richard Stallman a hard time in relation to some of his words about Jeffrey Epstein&rsquo;s case. Well, now I understand why those words may sound provocative and controversial but still, it&rsquo;s not cool to assault people and try to destroy their lives and careers just because they have different opinions. There is a huge difference between words and actions.</p> <p>Words aren&rsquo;t illegal, but stuff that Jeffrey Epstein did certainly is, and it&rsquo;s good to know that he suffered the consequences. Interestingly, some victims that appeared in that movie seemed quite controversial, and I wonder if it&rsquo;s intentional. Trying to frame everyone as either angels or daemons can do more harm than good because it destroys trust and trust is extremely important if we want to make people care about the crimes that were presented in that movie.</p> Duplicity https://bubelov.com/blog/2020/duplicity/ Sat, 25 Apr 2020 00:00:00 +0000 https://bubelov.com/blog/2020/duplicity/ <p>Self-hosting enables us to take back control over our data, but you might end up shooting yourself in the foot if you&rsquo;re not careful. Things happen: hardware failures, house fires, floods, tornadoes, earthquakes, you name it. We don&rsquo;t like to think about the bad stuff but people who&rsquo;re serious about self-hosting should really try to think like sysadmins and it means that data redundancy should be one of your biggest concerns.</p> <p>Storing data on a single device is risky and the simplest way to mitigate the risk of hardware failure is to buy an additional &ldquo;backup&rdquo; HDD or SSD, depending on the amount of data you need to back up and the amount of money in your wallet. A more advanced user can automate this routine with a simple script and by setting up a local NAS. Such a scheme can protect you from an accidental storage device failure but some events, such as house fire or tornado, can destroy both of your storage devices simultaneously and that&rsquo;s why we also need &ldquo;location redundancy&rdquo;.</p> <p>Storing your data in a separate location is not hard, but it comes with a different set of concerns. With a remote backup, you better have a good internet connection, and you should also trust the remote side, at least to a certain degree.</p> <p>So, here comes the need to minimize that trust. Of course, it&rsquo;s better to choose a reliable data storage provider and most of them won&rsquo;t lose your data because they&rsquo;re professionals, and they know how to handle your data, but they can snoop on it, or they can be hacked so your data might end up in the wrong hands. That&rsquo;s the real risk and that&rsquo;s why it&rsquo;s important not to store your backups in plaintext, anything that leaves your house should be encrypted in order to make it unreadable by anyone except yourself.</p> <p>That&rsquo;s quite a long introduction, I generally like to be verbose, it helps me to systematize my thoughts. So, apparently, there is a need for a remote backups which are unreadable by a remote storage provider. Is there a tool for that?</p> <p>Here comes <a href="http://duplicity.nongnu.org/">duplicity</a>. This tool can do incremental backups, and it also encrypts the data before syncing it with the remote servers. It supports many storage providers, including S3-based offerings such as this nice and shiny <a href="https://blog.scaleway.com/2020/behind-the-scenes-c14-cold-storage/">bunker 25 meters below Paris</a>. I played with this tool for a few days, and I really like how it works.</p> What Bitcoin Did https://bubelov.com/blog/2020/what-bitcoin-did/ Mon, 20 Apr 2020 00:00:00 +0000 https://bubelov.com/blog/2020/what-bitcoin-did/ <p>I&rsquo;ve checked out <a href="https://www.whatbitcoindid.com/">this podcast</a> a couple of weeks ago, and it seems to be balanced enough to serve as one of the main sources of bitcoin news for a person with an interest in bitcoin high enough to be in the loop but without getting too technical.</p> 2.5 Admins https://bubelov.com/blog/2020/2-5-admins/ Fri, 17 Apr 2020 00:00:00 +0000 https://bubelov.com/blog/2020/2-5-admins/ <p><a href="https://2.5admins.com/">2.5 Admins</a> is a new podcast by Joe Ressington from (now cancelled) Linux Action News. There is only one full episode available at the moment, but I liked it, and I think that the next episodes will be interesting too. This podcast might become a good source of tech news and sysadmin gossip.</p> Tiger King https://bubelov.com/blog/2020/tiger-king/ Fri, 10 Apr 2020 00:00:00 +0000 https://bubelov.com/blog/2020/tiger-king/ <p>It looks like the whole world have been watching this movie lately and there are zero reasons not to. I still can&rsquo;t believe that this Tiger King guy is real. Movies like this keep me from canceling my Netflix subscription, those guys are onto something, and they just continue to up their game.</p> Operation Odessa https://bubelov.com/blog/2020/operation-odessa/ Fri, 03 Apr 2020 00:00:00 +0000 https://bubelov.com/blog/2020/operation-odessa/ <p>I avoided clicking on this movie in my recommendations for a couple of years because, for some reason, I thought that it&rsquo;s some kind of political drama. Far from it! It&rsquo;s a criminal documentary, and it&rsquo;s actually pretty funny.</p> Password Auth https://bubelov.com/blog/2020/password-auth/ Tue, 25 Feb 2020 00:00:00 +0000 https://bubelov.com/blog/2020/password-auth/ <p>Email and password are two crucial pieces of data that almost every website or an app would ask you for in order to sign up. Most of those services save your email into their databases in plaintext and use it to send you <del>spam</del> very useful information on a constant basis. Knowing your email also enables those services to assist you if you forget your password. So far so good, but what happens when someone hacks those services? Unfortunately, such incidents happen more often than many people expect.</p> <p>Having your email stolen is unfortunate but having your password stolen can be more dangerous. That&rsquo;s because stolen passwords may be used to impersonate the users for a long time after a hack, completely without their notice. Another problem with having your password stolen is the fact that people tend to use the same password (or a few variations of a short list of passwords) to log in to many websites.</p> <p>I worked on many apps during my career, and they all required sending user passwords to an API in order to open new accounts or to sign in to existing ones. What happened next is still a mystery for me. Sometimes this information is not public and sometimes no one is really interested in knowing that. Ideally, those APIs should never save received passwords in plaintext but, unfortunately, it happens a lot.</p> <p><a href="https://www.theverge.com/2019/3/21/18275837/facebook-plain-text-password-storage-hundreds-millions-users">Facebook stored hundreds of millions of passwords in plain text</a></p> <p><a href="https://money.cnn.com/2012/06/06/technology/linkedin-password-hack/index.htm">More than 6 million LinkedIn passwords stolen</a></p> <p>Those are big wealthy companies, and even they tend to fuck up big time when it comes to storing passwords. When we write front end code, we usually assume that API is the ultimate source of truth, and we should make client code as dumb as possible. It&rsquo;s actually a good way of thinking about front end development because, in most of the cases, there is often nothing we can do to fix server errors without modifying server code.</p> <p>Well, it turned out, there is something we can do to prevent password leaking: just don&rsquo;t send user passwords to a server. Servers don&rsquo;t need to know user passwords and if they don&rsquo;t have this data, they won&rsquo;t be able to leak it. <a href="https://github.com/P-H-C/phc-winner-argon2">Argon2</a> is a great password hashing function, and we can just send Argon2 hashes of the original user passwords in place of the real plaintext passwords.</p> <p>This idea seemed a bit controversial, so I <a href="https://crypto.stackexchange.com/questions/77549/is-it-safe-to-use-a-deterministic-salt-as-an-input-to-kdf-argon2">asked around</a> on Cryptography Stack Exchange. I&rsquo;ve got a few interesting replies, and it looks like it isn&rsquo;t something unheard of. It&rsquo;s just not mainstream, and it really helps to protect user passwords from a database hack.</p> Plotting Financial Data in Python https://bubelov.com/blog/2020/plotting-financial-data-in-python/ Sun, 23 Feb 2020 00:00:00 +0000 https://bubelov.com/blog/2020/plotting-financial-data-in-python/ <p>Plotting price history, variance of returns, efficient frontier and other financial data in Python using Matplotlib and AlphaVantage API.</p> <p>Economics is one of my so-called &ldquo;practical hobbies&rdquo;. It&rsquo;s practical because it has a measurable positive impact on my life, and it&rsquo;s a hobby because it&rsquo;s not what I do professionally. I advise you to take my words with a grain of salt.</p> <p>I need to plot some financial data from time to time, and it&rsquo;s really hard to find a good and reasonably priced service which would have all the data and customization options I often need. Fortunately, it&rsquo;s not that hard to draw customized charts by yourself, especially with the help of the great tools such as <a href="https://www.python.org/">Python</a> and <a href="https://matplotlib.org/">Matplotlib</a>. This is a step-by-step guide which can help you to get started.</p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#source-code">Source Code</a></li> <li><a href="#picking-the-right-tools">Picking The Right Tools</a> <ul> <li><a href="#choosing-a-data-source">Choosing a Data Source</a></li> <li><a href="#programming-language">Programming Language</a></li> <li><a href="#plotting-library">Plotting Library</a></li> </ul> </li> <li><a href="#getting-started">Getting Started</a></li> <li><a href="#getting-alpha-vantage-api-key">Getting Alpha Vantage API key</a></li> <li><a href="#plotting-asset-price-history">Plotting Asset Price History</a> <ul> <li><a href="#api-wrapper">API Wrapper</a></li> <li><a href="#price-history">Price History</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </li> <li><a href="#plotting-risk-as-variance">Plotting Risk as Variance</a> <ul> <li><a href="#finding-variance">Finding Variance</a></li> <li><a href="#obtaining-the-data">Obtaining The Data</a></li> <li><a href="#plotting-the-data">Plotting The Data</a></li> <li><a href="#testing">Testing</a></li> <li><a href="#conclusion-1">Conclusion</a></li> </ul> </li> <li><a href="#comparing-asset-returns">Comparing Asset Returns</a> <ul> <li><a href="#data">Data</a></li> <li><a href="#adjusting-the-range">Adjusting The Range</a></li> <li><a href="#adjusting-the-values">Adjusting The Values</a></li> <li><a href="#plotting-the-data-1">Plotting The Data</a></li> <li><a href="#testing-1">Testing</a></li> <li><a href="#conclusion-2">Conclusion</a></li> </ul> </li> <li><a href="#plotting-efficient-frontier-2-assets">Plotting Efficient Frontier (2 assets)</a> <ul> <li><a href="#components">Components</a></li> <li><a href="#stats">Stats</a></li> <li><a href="#diversified-portfolio">Diversified Portfolio</a></li> <li><a href="#plotting-portfolios">Plotting Portfolios</a></li> <li><a href="#conclusion-3">Conclusion</a></li> </ul> </li> <li><a href="#plotting-efficient-frontier-n-assets">Plotting Efficient Frontier (N assets)</a> <ul> <li><a href="#why-diversify">Why Diversify?</a></li> <li><a href="#goal">Goal</a></li> <li><a href="#expected-return">Expected Return</a></li> <li><a href="#variance">Variance</a></li> <li><a href="#standard-deviation">Standard Deviation</a></li> <li><a href="#implementation">Implementation</a></li> <li><a href="#testing-2">Testing</a></li> <li><a href="#conclusion-4">Conclusion</a></li> </ul> </li> </ul> </nav> </div> <h2 id="source-code">Source Code</h2> <p>You can get the full source code here:</p> <p><a href="https://github.com/bubelov/market-plots">https://github.com/bubelov/market-plots</a></p> <h2 id="picking-the-right-tools">Picking The Right Tools</h2> <p>There are 3 choices that have to be made before we start coding:</p> <ol> <li>Choose a data source</li> <li>Choose a programming language</li> <li>Choose a visualization library</li> </ol> <h3 id="choosing-a-data-source">Choosing a Data Source</h3> <p>There are many financial data sources, but most of them aren&rsquo;t free. There is nothing wrong with paying for high quality data but let&rsquo;s start with a free API called <a href="https://www.alphavantage.co/">AlphaVantage</a>. I use it in this essay, but you can pick any other data source, it shouldn&rsquo;t affect most of the examples.</p> <h3 id="programming-language">Programming Language</h3> <p>I chose Python because it&rsquo;s frequently used for such tasks which means there should be plenty of mature tools available. It&rsquo;s also a really nice language for writing small utilities or integrating different parts of a big system. It can fail miserably in projects of substantial complexity but there is nothing to worry about if you just need to plot some data.</p> <h3 id="plotting-library">Plotting Library</h3> <p>There are plenty of libraries for plotting data. Here is the list from the Python wiki: <a href="https://wiki.python.org/moin/NumericAndScientific/Plotting">Plotting</a>. I chose <a href="https://matplotlib.org/">Matplotlib</a> since it&rsquo;s widely adopted, and it has everything that I need.</p> <h2 id="getting-started">Getting Started</h2> <p>This post assumes that you have Python 3 installed. If you have older version of Python, you&rsquo;re going to need some code adjustments.</p> <p>Let&rsquo;s create our project folder and give it a sensible name, such as <em>market-plots</em>:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">mkdir market-plots </span></span></code></pre></div><p>It&rsquo;s a good practice to isolate our little project from the rest of the system, so we won&rsquo;t mess with the global package registry.</p> <p>Here is how we can create a local environment for a scope of this project:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">cd market-plots </span></span><span class="line"><span class="cl">python3 -m venv venv </span></span></code></pre></div><p>This command will create a folder called <em>venv</em> which will contain our project-scoped dependencies.</p> <p>Let&rsquo;s activate our virtual environment:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">. venv/bin/activate </span></span></code></pre></div><p>You can always exit your virtual environment by executing this command:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">deactivate </span></span></code></pre></div><p>Now, let&rsquo;s create a text file where we&rsquo;ll list all the needed dependencies. By convention, it should be called <em>requirements.txt</em>:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">printf &#39;%s\n&#39; matplotlib &gt;&gt; requirements.txt </span></span><span class="line"><span class="cl">printf &#39;%s\n&#39; requests &gt;&gt; requirements.txt </span></span><span class="line"><span class="cl">printf &#39;%s\n&#39; python-dotenv &gt;&gt; requirements.txt </span></span></code></pre></div><p>So, we ended up with a file named <em>requirements.txt</em> which has the following contents:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">matplotlib </span></span><span class="line"><span class="cl">requests </span></span><span class="line"><span class="cl">python-dotenv </span></span></code></pre></div><p>We can install all of those dependencies by executing the following command:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">pip install -r requirements.txt </span></span></code></pre></div><p>Now we have everything ready so we can start coding.</p> <h2 id="getting-alpha-vantage-api-key">Getting Alpha Vantage API key</h2> <p>You can obtain a free API key <a href="https://www.alphavantage.co/">here</a> and add it to your project by executing the following command:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">echo ALPHA_VANTAGE_KEY=YOUR_API_KEY &gt; .env </span></span></code></pre></div><h2 id="plotting-asset-price-history">Plotting Asset Price History</h2> <p>There are many reasons why people are interested in asset price history. Some of those reasons are rational, and some are pretty bogus. Luckily for us, price history is very easy to plot, and we can also verify the correctness of a result just by looking at it, so it makes it a good warm-up exercise to get us comfortable with our new tools.</p> <h3 id="api-wrapper">API Wrapper</h3> <p>Let&rsquo;s create a new file and call it <em>alpha_vantage.py</em>. It will wrap <code>TIME_SERIES_*</code> list of functions, you can check the AlphaVantage API documentation for more details.</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python3" data-lang="python3"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">dotenv</span> <span class="kn">import</span> <span class="n">load_dotenv</span> </span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">os.path</span> <span class="kn">import</span> <span class="n">join</span><span class="p">,</span> <span class="n">dirname</span> </span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">dateutil</span> <span class="kn">import</span> <span class="n">parser</span> </span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">enum</span> <span class="kn">import</span> <span class="n">Enum</span> </span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span> </span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">os</span> </span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">urllib.request</span> <span class="k">as</span> <span class="nn">url_request</span> </span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">json</span> </span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span> </span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">ssl</span> </span></span><span class="line"><span class="cl"><span class="n">ssl</span><span class="o">.</span><span class="n">_create_default_https_context</span> <span class="o">=</span> <span class="n">ssl</span><span class="o">.</span><span class="n">_create_unverified_context</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="n">dotenv_path</span> <span class="o">=</span> <span class="n">join</span><span class="p">(</span><span class="n">dirname</span><span class="p">(</span><span class="vm">__file__</span><span class="p">),</span> <span class="s1">&#39;.env&#39;</span><span class="p">)</span> </span></span><span class="line"><span class="cl"><span class="n">load_dotenv</span><span class="p">(</span><span class="n">dotenv_path</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="n">API_KEY</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">getenv</span><span class="p">(</span><span class="s1">&#39;ALPHA_VANTAGE_KEY&#39;</span><span class="p">)</span> </span></span><span class="line"><span class="cl"><span class="n">REQUEST_TIMEOUT_SECONDS</span> <span class="o">=</span> <span class="mi">20</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Interval</span><span class="p">(</span><span class="n">Enum</span><span class="p">):</span> </span></span><span class="line"><span class="cl"> <span class="n">DAILY</span> <span class="o">=</span> <span class="s1">&#39;DAILY&#39;</span> </span></span><span class="line"><span class="cl"> <span class="n">WEEKLY</span> <span class="o">=</span> <span class="s1">&#39;WEEKLY&#39;</span> </span></span><span class="line"><span class="cl"> <span class="n">MONTHLY</span> <span class="o">=</span> <span class="s1">&#39;MONTHLY&#39;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="nd">@dataclass</span> </span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">AssetPrice</span><span class="p">:</span> </span></span><span class="line"><span class="cl"> <span class="n">date</span><span class="p">:</span> <span class="nb">str</span> </span></span><span class="line"><span class="cl"> <span class="n">price</span><span class="p">:</span> <span class="nb">float</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_stock_price_history</span><span class="p">(</span><span class="n">symbol</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">interval</span><span class="p">:</span> <span class="n">Interval</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">adjusted</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">AssetPrice</span><span class="p">]:</span> </span></span><span class="line"><span class="cl"> <span class="n">url</span> <span class="o">=</span> <span class="n">url_for_function</span><span class="p">(</span><span class="s1">&#39;TIME_SERIES_</span><span class="si">%s</span><span class="s1">&#39;</span> <span class="o">%</span> <span class="n">interval</span><span class="o">.</span><span class="n">value</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="n">adjusted</span> <span class="o">==</span> <span class="kc">True</span><span class="p">:</span> </span></span><span class="line"><span class="cl"> <span class="n">url</span> <span class="o">+=</span> <span class="s1">&#39;_ADJUSTED&#39;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">url</span> <span class="o">+=</span> <span class="s1">&#39;&amp;apikey=</span><span class="si">%s</span><span class="s1">&#39;</span> <span class="o">%</span> <span class="n">API_KEY</span> </span></span><span class="line"><span class="cl"> <span class="n">url</span> <span class="o">+=</span> <span class="s1">&#39;&amp;symbol=</span><span class="si">%s</span><span class="s1">&#39;</span> <span class="o">%</span> <span class="n">symbol</span> </span></span><span class="line"><span class="cl"> <span class="n">url</span> <span class="o">+=</span> <span class="s1">&#39;&amp;outputsize=full&#39;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">response</span> <span class="o">=</span> <span class="n">url_request</span><span class="o">.</span><span class="n">urlopen</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="n">REQUEST_TIMEOUT_SECONDS</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">data</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">response</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">prices_json</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="nb">list</span><span class="p">(</span><span class="n">data</span><span class="o">.</span><span class="n">keys</span><span class="p">())[</span><span class="mi">1</span><span class="p">]]</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">field_name</span> <span class="o">=</span> <span class="s1">&#39;4. close&#39;</span> <span class="k">if</span> <span class="n">adjusted</span> <span class="o">==</span> <span class="kc">False</span> <span class="k">else</span> <span class="s1">&#39;5. adjusted close&#39;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">prices</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">AssetPrice</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="n">k</span><span class="p">,</span> <span class="n">v</span> <span class="ow">in</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">prices_json</span><span class="o">.</span><span class="n">items</span><span class="p">()):</span> </span></span><span class="line"><span class="cl"> <span class="n">prices</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">AssetPrice</span><span class="p">(</span><span class="n">date</span><span class="o">=</span><span class="n">parser</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">k</span><span class="p">),</span> </span></span><span class="line"><span class="cl"> <span class="n">price</span><span class="o">=</span><span class="nb">float</span><span class="p">(</span><span class="n">v</span><span class="p">[</span><span class="n">field_name</span><span class="p">])))</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="n">prices</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">url_for_function</span><span class="p">(</span><span class="n">function</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="sa">f</span><span class="s1">&#39;https://www.alphavantage.co/query?function=</span><span class="si">{</span><span class="n">function</span><span class="si">}</span><span class="s1">&#39;</span> </span></span></code></pre></div><p>The logic inside this file is quite straightforward. Let&rsquo;s examine all the arguments to understand how to use this function properly:</p> <ul> <li><code>symbol</code> - it&rsquo;s just a stock trading symbol such as GOOG, TSLA and so on</li> <li><code>interval</code> - sampling interval, you can use <code>DAILY</code>, <code>WEEKLY</code> or <code>MONTHLY</code> intervals</li> <li><code>adjusted</code> - whether to use an absolute price or adjust for stock splits and dividends</li> </ul> <h3 id="price-history">Price History</h3> <p>Now since we have a financial API wrapper, let&rsquo;s use it to plot stock price history. We need to add a new file called <em>history.py</em> which should contain the following code:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python3" data-lang="python3"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">sys</span> </span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pathlib</span> </span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">matplotlib.pyplot</span> <span class="k">as</span> <span class="nn">plt</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">alpha_vantage</span> </span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">alpha_vantage</span> <span class="kn">import</span> <span class="n">Interval</span> </span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">plot_style</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">show_history</span><span class="p">(</span><span class="n">symbol</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span> </span></span><span class="line"><span class="cl"> <span class="n">data</span> <span class="o">=</span> <span class="n">alpha_vantage</span><span class="o">.</span><span class="n">get_stock_price_history</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">symbol</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">Interval</span><span class="o">.</span><span class="n">MONTHLY</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">adjusted</span><span class="o">=</span><span class="kc">False</span> </span></span><span class="line"><span class="cl"> <span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">plot_style</span><span class="o">.</span><span class="n">line</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">title</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;</span><span class="si">{</span><span class="n">symbol</span><span class="o">.</span><span class="n">upper</span><span class="p">()</span><span class="si">}</span><span class="s1"> Price History&#39;</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">plot</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="nb">list</span><span class="p">(</span><span class="n">i</span><span class="o">.</span><span class="n">date</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">data</span><span class="p">),</span> </span></span><span class="line"><span class="cl"> <span class="nb">list</span><span class="p">(</span><span class="n">i</span><span class="o">.</span><span class="n">price</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">data</span><span class="p">))</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="s1">&#39;img/history&#39;</span><span class="p">)</span><span class="o">.</span><span class="n">mkdir</span><span class="p">(</span><span class="n">parents</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">exist_ok</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">savefig</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;img/history/</span><span class="si">{</span><span class="n">symbol</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span><span class="si">}</span><span class="s1">.png&#39;</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">close</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="n">show_history</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span> </span></span></code></pre></div><p>Let&rsquo;s test this script by giving it a real query. We can look up the S&amp;P 500 index history by passing its symbol (<code>SPX</code>) as an argument to our new script:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">python history.py spx </span></span></code></pre></div><p>You should see the chart similar to this one:</p> <p><figure> <a href="https://bubelov.com/blog/2020/plotting-financial-data-in-python/spx-history_hu_738268286d5eb144.webp"> <img src="https://bubelov.com/blog/2020/plotting-financial-data-in-python/spx-history_hu_eeeffb594dba233b.webp" alt="" /> </a> <figcaption>SPX price history</figcaption> </figure></p> <h3 id="conclusion">Conclusion</h3> <p>We&rsquo;ve created a simple wrapper that allows us to query stock price history and used it to plot the data on screen. Next, we&rsquo;ll use this data to show the risk of different stocks.</p> <h2 id="plotting-risk-as-variance">Plotting Risk as Variance</h2> <p>Variance is an important indicator if you want to know the level of risk associated with holding a given security. It&rsquo;s important to understand that past variance might not be a good predictor of future variance, but most of the time it works, and we don&rsquo;t have other options anyway. Let&rsquo;s create a script for displaying returns distribution, variance and standard deviation of any given security.</p> <h3 id="finding-variance">Finding Variance</h3> <p>It&rsquo;s super easy to find a variance if you have the returns&rsquo; data. Here is how to do that:</p> <ol> <li>Find mean returns (mean of all data points).</li> <li>For each data point, subtract its value from the mean returns and square the result of subtraction (note that we made it impossible to have a negative result since no number in the power of 2 can be negative).</li> <li>Sum all the results and divide this sum by the number of data points.</li> </ol> <p>That&rsquo;s all, now you have a variance. If you also want to find a standard deviation, just take the square root of a variance value.</p> <h3 id="obtaining-the-data">Obtaining The Data</h3> <p>Let&rsquo;s extend our <code>alpha_vantage.py</code> module to add one more function:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python3" data-lang="python3"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_stock_returns_history</span><span class="p">(</span><span class="n">symbol</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">interval</span><span class="p">:</span> <span class="n">Interval</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="p">[</span><span class="nb">float</span><span class="p">]:</span> </span></span><span class="line"><span class="cl"> <span class="n">price_history</span> <span class="o">=</span> <span class="n">get_stock_price_history</span><span class="p">(</span><span class="n">symbol</span><span class="p">,</span> <span class="n">interval</span><span class="p">,</span> <span class="n">adjusted</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">returns</span><span class="p">:</span> <span class="p">[</span><span class="nb">float</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span> </span></span><span class="line"><span class="cl"> <span class="n">prev_price</span> <span class="o">=</span> <span class="kc">None</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">price_history</span><span class="p">:</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="n">prev_price</span> <span class="o">!=</span> <span class="kc">None</span><span class="p">:</span> </span></span><span class="line"><span class="cl"> <span class="n">returns</span><span class="o">.</span><span class="n">append</span><span class="p">((</span><span class="n">item</span><span class="o">.</span><span class="n">price</span> <span class="o">-</span> <span class="n">prev_price</span><span class="p">)</span> <span class="o">/</span> <span class="n">prev_price</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">prev_price</span> <span class="o">=</span> <span class="n">item</span><span class="o">.</span><span class="n">price</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="n">returns</span> </span></span></code></pre></div><p>This data is based on the price history data, but now we&rsquo;re not interested in absolute numbers, so we have to calculate relative changes and return them as a simple array. For instance, with a <code>MONTHLY</code> interval this array would contain the month-to-month changes in the price of a given security.</p> <h3 id="plotting-the-data">Plotting The Data</h3> <p>Let&rsquo;s create a new file and call it <code>variance.py</code>, it should contain the following code:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python3" data-lang="python3"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">sys</span> </span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span> </span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">matplotlib</span> </span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">matplotlib.pyplot</span> <span class="k">as</span> <span class="nn">plt</span> </span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pathlib</span> </span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">matplotlib.style</span> <span class="k">as</span> <span class="nn">style</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">alpha_vantage</span> </span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">alpha_vantage</span> <span class="kn">import</span> <span class="n">Interval</span> </span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">plot_style</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">show_variance</span><span class="p">(</span><span class="n">symbol</span><span class="p">,</span> <span class="n">interval</span><span class="o">=</span><span class="n">Interval</span><span class="o">.</span><span class="n">MONTHLY</span><span class="p">):</span> </span></span><span class="line"><span class="cl"> <span class="n">returns</span> <span class="o">=</span> <span class="n">alpha_vantage</span><span class="o">.</span><span class="n">get_stock_returns_history</span><span class="p">(</span><span class="n">symbol</span><span class="p">,</span> <span class="n">interval</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">variance</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">var</span><span class="p">(</span><span class="n">returns</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">standard_deviation</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">sqrt</span><span class="p">(</span><span class="n">variance</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">mean_return</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">mean</span><span class="p">(</span><span class="n">returns</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">plot_style</span><span class="o">.</span><span class="n">hist</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">n</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">patches</span> <span class="o">=</span> <span class="n">plt</span><span class="o">.</span><span class="n">hist</span><span class="p">(</span><span class="n">returns</span><span class="p">,</span> <span class="n">density</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">bins</span><span class="o">=</span><span class="mi">25</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">patches</span><span class="p">:</span> </span></span><span class="line"><span class="cl"> <span class="n">item</span><span class="o">.</span><span class="n">set_height</span><span class="p">(</span><span class="n">item</span><span class="o">.</span><span class="n">get_height</span><span class="p">()</span> <span class="o">/</span> <span class="nb">sum</span><span class="p">(</span><span class="n">n</span><span class="p">))</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">max_y</span> <span class="o">=</span> <span class="nb">max</span><span class="p">(</span><span class="n">n</span><span class="p">)</span> <span class="o">/</span> <span class="nb">sum</span><span class="p">(</span><span class="n">n</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">ylim</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">max_y</span> <span class="o">+</span> <span class="n">max_y</span> <span class="o">/</span> <span class="mi">10</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">gca</span><span class="p">()</span><span class="o">.</span><span class="n">set_xticklabels</span><span class="p">([</span><span class="s1">&#39;</span><span class="si">{:.0f}</span><span class="s1">%&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">x</span><span class="o">*</span><span class="mi">100</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">plt</span><span class="o">.</span><span class="n">gca</span><span class="p">()</span><span class="o">.</span><span class="n">get_xticks</span><span class="p">()])</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">gca</span><span class="p">()</span><span class="o">.</span><span class="n">set_yticklabels</span><span class="p">([</span><span class="s1">&#39;</span><span class="si">{:.0f}</span><span class="s1">%&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">y</span><span class="o">*</span><span class="mi">100</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="n">y</span> <span class="ow">in</span> <span class="n">plt</span><span class="o">.</span><span class="n">gca</span><span class="p">()</span><span class="o">.</span><span class="n">get_yticks</span><span class="p">()])</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">title_line_1</span> <span class="o">=</span> <span class="sa">f</span><span class="s1">&#39;</span><span class="si">{</span><span class="n">symbol</span><span class="o">.</span><span class="n">upper</span><span class="p">()</span><span class="si">}</span><span class="s1"> </span><span class="si">{</span><span class="n">interval</span><span class="o">.</span><span class="n">value</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span><span class="o">.</span><span class="n">capitalize</span><span class="p">()</span><span class="si">}</span><span class="s1"> Return Distribution&#39;</span> </span></span><span class="line"><span class="cl"> <span class="n">title_line_2</span> <span class="o">=</span> <span class="s1">&#39;Standard Deviation = </span><span class="si">%.2f%%</span><span class="s1"> Mean Return = </span><span class="si">%.2f%%</span><span class="s1">&#39;</span> <span class="o">%</span> <span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">standard_deviation</span> <span class="o">*</span> <span class="mi">100</span><span class="p">,</span> <span class="n">mean_return</span> <span class="o">*</span> <span class="mi">100</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">title</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;</span><span class="si">{</span><span class="n">title_line_1</span><span class="si">}</span><span class="se">\n</span><span class="si">{</span><span class="n">title_line_2</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">xlabel</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;</span><span class="si">{</span><span class="n">interval</span><span class="o">.</span><span class="n">value</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span><span class="o">.</span><span class="n">capitalize</span><span class="p">()</span><span class="si">}</span><span class="s1"> Return&#39;</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">ylabel</span><span class="p">(</span><span class="s1">&#39;Probability&#39;</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="s1">&#39;img/variance&#39;</span><span class="p">)</span><span class="o">.</span><span class="n">mkdir</span><span class="p">(</span><span class="n">parents</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">exist_ok</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">savefig</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;img/variance/</span><span class="si">{</span><span class="n">symbol</span><span class="si">}</span><span class="s1">.png&#39;</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">close</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="n">show_variance</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span> </span></span></code></pre></div><p>First, this script checks the number of input parameters, we need this check to find out whether we have a period specified or should we use the default value (<code>MONTHLY</code>). Next, this code fetches the returns&rsquo; history data and calculates the variance based on that data. The final step is to plot the returns&rsquo; distribution as a histogram, so we can see the relative frequencies of any given returns.</p> <h3 id="testing">Testing</h3> <p>Let&rsquo;s test our new module by requiring it to draw a couple of charts:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ python variance.py tsla </span></span></code></pre></div><p>You should see the chart similar to this one:</p> <p><figure> <a href="https://bubelov.com/blog/2020/plotting-financial-data-in-python/tsla-variance_hu_cf450790eb286075.webp"> <img src="https://bubelov.com/blog/2020/plotting-financial-data-in-python/tsla-variance_hu_89cb279de3e5201d.webp" alt="" /> </a> </figure></p> <p>Now let&rsquo;s check the variance of S&amp;P 500 (<code>SPX</code>) index:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ python variance.py spx </span></span></code></pre></div><p><figure> <a href="https://bubelov.com/blog/2020/plotting-financial-data-in-python/spx-variance_hu_de571bf6a3c7d56a.webp"> <img src="https://bubelov.com/blog/2020/plotting-financial-data-in-python/spx-variance_hu_4f810152adc7d84a.webp" alt="" /> </a> </figure></p> <p>It&rsquo;s easy to see why <code>TSLA</code> is more risky to hold than <code>SPX</code> but it does not mean that it&rsquo;s a bad choice. Why does anyone want to hold such an unpredictable stock? You can look at the mean returns or plot the price history to see the answer.</p> <h3 id="conclusion-1">Conclusion</h3> <p>Now that we can get a hint of the risk of holding any particular security, it would be nice to have a way of comparing the returns. It might be a good idea to hold a risky security if it gives exceptional returns, and you are not worried about price volatility in the short term.</p> <h2 id="comparing-asset-returns">Comparing Asset Returns</h2> <p>It&rsquo;s hard to compare the returns on the given securities just by looking at their price history in absolute terms, so we need to find a better way of comparing historical returns. One of the possible solutions is to adjust the whole data series in such a way that the first data point would be equal to some predefined number. We&rsquo;re going to adjust the price history of both assets, so they will always start with the same value, let&rsquo;s say 100. This method allows us to see what would happen with $100 invested in 2 given stocks on the date D.</p> <h3 id="data">Data</h3> <p>You should already have a function for retrieving stock price history, if you don&rsquo;t have it - go to <a href="#price-history">Price History</a> and implement it. It should return all the data we need, but it has 2 major issues:</p> <ol> <li>The values can have different date ranges, we need to use the same range for both datasets.</li> <li>The values are in absolute terms, so we need to adjust them to start with the same value but keep the original proportions.</li> </ol> <h3 id="adjusting-the-range">Adjusting The Range</h3> <p>The most obvious way to solve the range mismatch is to cut out all the dates that are not present in both data sets. Here is the possible solution:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python3" data-lang="python3"><span class="line"><span class="cl"><span class="n">history_1</span> <span class="o">=</span> <span class="n">alpha_vantage</span><span class="o">.</span><span class="n">get_stock_price_history</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">symbol_1</span><span class="p">,</span> <span class="n">interval</span><span class="p">,</span> <span class="n">adjusted</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="n">history_2</span> <span class="o">=</span> <span class="n">alpha_vantage</span><span class="o">.</span><span class="n">get_stock_price_history</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">symbol_2</span><span class="p">,</span> <span class="n">interval</span><span class="p">,</span> <span class="n">adjusted</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="n">history_1</span> <span class="o">=</span> <span class="p">[</span><span class="n">v</span> <span class="k">for</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">history_1</span> <span class="k">if</span> <span class="n">v</span><span class="o">.</span><span class="n">date</span> <span class="ow">in</span> <span class="nb">list</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">i</span><span class="o">.</span><span class="n">date</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">history_2</span><span class="p">)]</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="n">history_2</span> <span class="o">=</span> <span class="p">[</span><span class="n">v</span> <span class="k">for</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">history_2</span> <span class="k">if</span> <span class="n">v</span><span class="o">.</span><span class="n">date</span> <span class="ow">in</span> <span class="nb">list</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">i</span><span class="o">.</span><span class="n">date</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">history_1</span><span class="p">)]</span> </span></span></code></pre></div><p>We just filter each dataset and keep only the dates that are present in the other dataset. Now we can be sure that we&rsquo;re comparing the same date range.</p> <h3 id="adjusting-the-values">Adjusting The Values</h3> <p>Different stocks can have different prices at any given time and if some stock is selling for $1000 per share it does not mean that it&rsquo;s better than the stock trading for $1 per share. It&rsquo;s all relative, so we have to scale the data before we compare it.</p> <p>Here&rsquo;s how we can do that:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python3" data-lang="python3"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">adjust_values</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">AssetPrice</span><span class="p">],</span> <span class="n">start</span><span class="p">:</span> <span class="nb">float</span><span class="p">):</span> </span></span><span class="line"><span class="cl"> <span class="n">scale_factor</span> <span class="o">=</span> <span class="kc">None</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">data</span><span class="p">:</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="n">scale_factor</span> <span class="o">==</span> <span class="kc">None</span><span class="p">:</span> </span></span><span class="line"><span class="cl"> <span class="n">scale_factor</span> <span class="o">=</span> <span class="n">v</span><span class="o">.</span><span class="n">price</span> <span class="o">/</span> <span class="n">start</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">v</span><span class="o">.</span><span class="n">price</span> <span class="o">=</span> <span class="n">v</span><span class="o">.</span><span class="n">price</span> <span class="o">/</span> <span class="n">scale_factor</span> </span></span></code></pre></div><h3 id="plotting-the-data-1">Plotting The Data</h3> <p>Let&rsquo;s create a new file and name it <code>compare.py</code>:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python3" data-lang="python3"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">sys</span> </span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pathlib</span> </span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">matplotlib.pyplot</span> <span class="k">as</span> <span class="nn">plt</span> </span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">alpha_vantage</span> </span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">alpha_vantage</span> <span class="kn">import</span> <span class="n">Interval</span><span class="p">,</span> <span class="n">AssetPrice</span> </span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">plot_style</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">compare</span><span class="p">(</span><span class="n">symbol_1</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">symbol_2</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">interval</span><span class="o">=</span><span class="n">Interval</span><span class="o">.</span><span class="n">MONTHLY</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">adjusted</span><span class="o">=</span><span class="kc">True</span><span class="p">):</span> </span></span><span class="line"><span class="cl"> <span class="n">history_1</span> <span class="o">=</span> <span class="n">alpha_vantage</span><span class="o">.</span><span class="n">get_stock_price_history</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">symbol_1</span><span class="p">,</span> <span class="n">interval</span><span class="p">,</span> <span class="n">adjusted</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">history_2</span> <span class="o">=</span> <span class="n">alpha_vantage</span><span class="o">.</span><span class="n">get_stock_price_history</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">symbol_2</span><span class="p">,</span> <span class="n">interval</span><span class="p">,</span> <span class="n">adjusted</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">history_1</span> <span class="o">=</span> <span class="p">[</span><span class="n">v</span> <span class="k">for</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">history_1</span> <span class="k">if</span> <span class="n">v</span><span class="o">.</span><span class="n">date</span> <span class="ow">in</span> <span class="nb">list</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">i</span><span class="o">.</span><span class="n">date</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">history_2</span><span class="p">)]</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">history_2</span> <span class="o">=</span> <span class="p">[</span><span class="n">v</span> <span class="k">for</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">history_2</span> <span class="k">if</span> <span class="n">v</span><span class="o">.</span><span class="n">date</span> <span class="ow">in</span> <span class="nb">list</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">i</span><span class="o">.</span><span class="n">date</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">history_1</span><span class="p">)]</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">adjust_values</span><span class="p">(</span><span class="n">history_1</span><span class="p">,</span> <span class="n">start</span><span class="o">=</span><span class="mf">100.0</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">adjust_values</span><span class="p">(</span><span class="n">history_2</span><span class="p">,</span> <span class="n">start</span><span class="o">=</span><span class="mf">100.0</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">plot_style</span><span class="o">.</span><span class="n">line</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">plot</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="nb">list</span><span class="p">(</span><span class="n">i</span><span class="o">.</span><span class="n">date</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">history_1</span><span class="p">),</span> </span></span><span class="line"><span class="cl"> <span class="nb">list</span><span class="p">(</span><span class="n">i</span><span class="o">.</span><span class="n">price</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">history_1</span><span class="p">),</span> </span></span><span class="line"><span class="cl"> <span class="n">label</span><span class="o">=</span><span class="n">symbol_1</span><span class="o">.</span><span class="n">upper</span><span class="p">())</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">plot</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="nb">list</span><span class="p">(</span><span class="n">i</span><span class="o">.</span><span class="n">date</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">history_2</span><span class="p">),</span> </span></span><span class="line"><span class="cl"> <span class="nb">list</span><span class="p">(</span><span class="n">i</span><span class="o">.</span><span class="n">price</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">history_2</span><span class="p">),</span> </span></span><span class="line"><span class="cl"> <span class="n">label</span><span class="o">=</span><span class="n">symbol_2</span><span class="o">.</span><span class="n">upper</span><span class="p">())</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">title</span> <span class="o">=</span> <span class="sa">f</span><span class="s1">&#39;</span><span class="si">{</span><span class="n">symbol_1</span><span class="o">.</span><span class="n">upper</span><span class="p">()</span><span class="si">}</span><span class="s1"> vs </span><span class="si">{</span><span class="n">symbol_2</span><span class="o">.</span><span class="n">upper</span><span class="p">()</span><span class="si">}</span><span class="s1">&#39;</span> </span></span><span class="line"><span class="cl"> <span class="n">title</span> <span class="o">=</span> <span class="n">title</span> <span class="o">+</span> <span class="s1">&#39; (adjusted)&#39;</span> <span class="k">if</span> <span class="n">adjusted</span> <span class="o">==</span> <span class="kc">True</span> <span class="k">else</span> <span class="n">title</span> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">title</span><span class="p">(</span><span class="n">title</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">legend</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="s1">&#39;img/compare&#39;</span><span class="p">)</span><span class="o">.</span><span class="n">mkdir</span><span class="p">(</span><span class="n">parents</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">exist_ok</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">savefig</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;img/compare/</span><span class="si">{</span><span class="n">symbol_1</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span><span class="si">}</span><span class="s1">-</span><span class="si">{</span><span class="n">symbol_2</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span><span class="si">}</span><span class="s1">.png&#39;</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">close</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">adjust_values</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">AssetPrice</span><span class="p">],</span> <span class="n">start</span><span class="p">:</span> <span class="nb">float</span><span class="p">):</span> </span></span><span class="line"><span class="cl"> <span class="n">scale_factor</span> <span class="o">=</span> <span class="kc">None</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">data</span><span class="p">:</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="n">scale_factor</span> <span class="o">==</span> <span class="kc">None</span><span class="p">:</span> </span></span><span class="line"><span class="cl"> <span class="n">scale_factor</span> <span class="o">=</span> <span class="n">v</span><span class="o">.</span><span class="n">price</span> <span class="o">/</span> <span class="n">start</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">v</span><span class="o">.</span><span class="n">price</span> <span class="o">=</span> <span class="n">v</span><span class="o">.</span><span class="n">price</span> <span class="o">/</span> <span class="n">scale_factor</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="n">compare</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">2</span><span class="p">])</span> </span></span></code></pre></div><p>This code combines the previous steps and also draws the resulting datasets on the screen.</p> <h3 id="testing-1">Testing</h3> <p>Let&rsquo;s test this module by comparing the Tesla Inc cumulative returns using the S&amp;P 500 index as a benchmark:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">python compare.py spx tsla </span></span></code></pre></div><p>You should see the chart similar to this one:</p> <p><figure> <a href="https://bubelov.com/blog/2020/plotting-financial-data-in-python/compare-tsla-spx_hu_349ef4df2724fdac.webp"> <img src="https://bubelov.com/blog/2020/plotting-financial-data-in-python/compare-tsla-spx_hu_e5acf13d7beca663.webp" alt="" /> </a> </figure></p> <p>It seems like the <code>TSLA</code> stock made much more money for its shareholders than the <code>SPX</code> index, but it also looks more volatile and jittery.</p> <h3 id="conclusion-2">Conclusion</h3> <p>Now we have a tool for comparing asset performance, and it also gives us a hint about the historical volatility. Next, we&rsquo;ll create a tool for measuring the &ldquo;compatibility&rdquo; of 2 given stocks. Expected returns might be a good hint on what to buy, but it&rsquo;s also important to understand the relationship between different stocks in a portfolio.</p> <h2 id="plotting-efficient-frontier-2-assets">Plotting Efficient Frontier (2 assets)</h2> <p>The main idea behind the Efficient Frontier is that the overall risk (volatility) of a portfolio may not be equal to the sum of the risk of its components, so some combinations are better than others. In this post we&rsquo;re going to visualize the optimal weights of 2 given assets in a hypothetical portfolio.</p> <h3 id="components">Components</h3> <p>Let&rsquo;s compare <code>IBM</code> and Disney (<code>DIS</code>):</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">python compare.py ibm dis </span></span></code></pre></div><p><figure> <a href="https://bubelov.com/blog/2020/plotting-financial-data-in-python/compare-ibm-dis_hu_5d0d750f59abe1a2.webp"> <img src="https://bubelov.com/blog/2020/plotting-financial-data-in-python/compare-ibm-dis_hu_e61a13cb90c498a5.webp" alt="" /> </a> <figcaption>IBM and DIS chart</figcaption> </figure></p> <p>It looks like both of those stocks have made a lot of money for their shareholders, but it&rsquo;s unclear who will be better off in the long run. Let&rsquo;s analyze both stocks in order to make a few assumptions based on their historical performance.</p> <h3 id="stats">Stats</h3> <p>Here are some stats for <code>IBM</code>:</p> <ul> <li>Monthly mean return: 0.76%</li> <li>Standard deviation of monthly return: 7.51%</li> </ul> <p>Here are the same stats tor <code>DIS</code>:</p> <ul> <li>Monthly mean return: 0.83%</li> <li>Standard deviation of monthly return: 7.25%</li> </ul> <p>Those stocks look pretty close in terms of risk and return so which one should we choose? Would it be a better idea to split the portfolio 50/50? Let&rsquo;s run a few calculations to find out.</p> <h3 id="diversified-portfolio">Diversified Portfolio</h3> <p>So what are the mean return of the 50/50 portfolio? It&rsquo;s just a weighted average of its components&rsquo; return:</p> <p>$$ r_p = r_1 w_1 + r_2 w_2 $$</p> <p>Where:</p> <p>\( r_p \) = portfolio mean return</p> <p>\( r_1, r_2 \) = return of the portfolio components</p> <p>\( w_1, w_2 \) = weights of the portfolio components</p> <p>0.76% * 50% + 0.83% * 50% = 0.80%</p> <p>Not bad, but what about the risk? Here is the formula:</p> <p>$$ σ_p^2 = w_1^2 σ_1^2 + w_2^2 σ_2^2 + 2 w_1 w_2 cov(1, 2) $$</p> <p>In our case, <em>covariance</em> is 0.00207, so:</p> <ul> <li><em>variance</em> = 0.5^2 * 0.0751^2 + 0.5^2 * 0.0725^2 + 2 * 0.5 * 0.5 * 0.00207 = 0.003759065</li> <li><em>standard deviation</em> = 6.13% (square root of <em>variance</em>)</li> </ul> <p>It looks like the 50/50 split is not a bad idea after all, we would get the same return with significantly less risk but what about the other possible combinations? How much would we get if we were ready to accept more risk? Is it possible to decrease the risk? Plotting many combinations on a graph might give us a good picture of how diversification works and helps us to make the right choice.</p> <h3 id="plotting-portfolios">Plotting Portfolios</h3> <p>Let&rsquo;s plot a lot of different asset combinations in order to find out how they affect portfolio volatility and expected return. A scatter plot is a good tool for showing that kind of data. Here is the script, you can call it <code>frontier2.py</code>:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python3" data-lang="python3"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">sys</span> </span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pathlib</span> </span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span> </span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">matplotlib</span> </span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">matplotlib.pyplot</span> <span class="k">as</span> <span class="nn">plt</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">alpha_vantage</span> </span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">alpha_vantage</span> <span class="kn">import</span> <span class="n">Interval</span> </span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">plot_style</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">show_frontier</span><span class="p">(</span><span class="n">symbol1</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">symbol2</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">interval</span><span class="o">=</span><span class="n">Interval</span><span class="o">.</span><span class="n">MONTHLY</span><span class="p">):</span> </span></span><span class="line"><span class="cl"> <span class="n">returns1</span> <span class="o">=</span> <span class="n">alpha_vantage</span><span class="o">.</span><span class="n">get_stock_returns_history</span><span class="p">(</span><span class="n">symbol1</span><span class="p">,</span> <span class="n">interval</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">returns2</span> <span class="o">=</span> <span class="n">alpha_vantage</span><span class="o">.</span><span class="n">get_stock_returns_history</span><span class="p">(</span><span class="n">symbol2</span><span class="p">,</span> <span class="n">interval</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">returns1</span><span class="p">)</span> <span class="o">&gt;</span> <span class="nb">len</span><span class="p">(</span><span class="n">returns2</span><span class="p">):</span> </span></span><span class="line"><span class="cl"> <span class="n">returns1</span> <span class="o">=</span> <span class="n">returns1</span><span class="p">[</span><span class="o">-</span><span class="nb">len</span><span class="p">(</span><span class="n">returns2</span><span class="p">):]</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">returns2</span><span class="p">)</span> <span class="o">&gt;</span> <span class="nb">len</span><span class="p">(</span><span class="n">returns1</span><span class="p">):</span> </span></span><span class="line"><span class="cl"> <span class="n">returns2</span> <span class="o">=</span> <span class="n">returns2</span><span class="p">[</span><span class="o">-</span><span class="nb">len</span><span class="p">(</span><span class="n">returns1</span><span class="p">):]</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">mean_returns1</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">mean</span><span class="p">(</span><span class="n">returns1</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">variance1</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">var</span><span class="p">(</span><span class="n">returns1</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">standard_deviation1</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">sqrt</span><span class="p">(</span><span class="n">variance1</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1">#print(f&#39;Mean returns ({symbol1}) = {mean_returns1}&#39;)</span> </span></span><span class="line"><span class="cl"> <span class="c1">#print(f&#39;Variance ({symbol1}) = {variance1}&#39;)</span> </span></span><span class="line"><span class="cl"> <span class="c1">#print(f&#39;Standard Deviation ({symbol1}) = {standard_deviation1}&#39;)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">mean_returns2</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">mean</span><span class="p">(</span><span class="n">returns2</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">variance2</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">var</span><span class="p">(</span><span class="n">returns2</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">standard_deviation2</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">sqrt</span><span class="p">(</span><span class="n">variance2</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1">#print(f&#39;Mean returns ({symbol2}) = {mean_returns2}&#39;)</span> </span></span><span class="line"><span class="cl"> <span class="c1">#print(f&#39;Variance ({symbol2}) = {variance2}&#39;)</span> </span></span><span class="line"><span class="cl"> <span class="c1">#print(f&#39;Standard Deviation ({symbol2}) = {standard_deviation2}&#39;)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">correlation</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">corrcoef</span><span class="p">(</span><span class="n">returns1</span><span class="p">,</span> <span class="n">returns2</span><span class="p">)[</span><span class="mi">0</span><span class="p">][</span><span class="mi">1</span><span class="p">]</span> </span></span><span class="line"><span class="cl"> <span class="c1">#print(f&#39;Corellation = {correlation}&#39;)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">weights</span> <span class="o">=</span> <span class="p">[]</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">101</span><span class="p">):</span> </span></span><span class="line"><span class="cl"> <span class="n">weights</span><span class="o">.</span><span class="n">append</span><span class="p">((</span><span class="mi">1</span> <span class="o">-</span> <span class="mf">0.01</span> <span class="o">*</span> <span class="n">n</span><span class="p">,</span> <span class="mi">0</span> <span class="o">+</span> <span class="mf">0.01</span> <span class="o">*</span> <span class="n">n</span><span class="p">))</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">returns</span> <span class="o">=</span> <span class="p">[]</span> </span></span><span class="line"><span class="cl"> <span class="n">standard_deviations</span> <span class="o">=</span> <span class="p">[]</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">portfolio_50_50_standard_deviation</span> <span class="o">=</span> <span class="kc">None</span> </span></span><span class="line"><span class="cl"> <span class="n">portfolio_50_50_returns</span> <span class="o">=</span> <span class="kc">None</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">plot_style</span><span class="o">.</span><span class="n">scatter</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="n">w1</span><span class="p">,</span> <span class="n">w2</span> <span class="ow">in</span> <span class="n">weights</span><span class="p">:</span> </span></span><span class="line"><span class="cl"> <span class="n">returns</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">w1</span> <span class="o">*</span> <span class="n">mean_returns1</span> <span class="o">+</span> <span class="n">w2</span> <span class="o">*</span> <span class="n">mean_returns2</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">variance</span> <span class="o">=</span> <span class="n">w1</span><span class="o">**</span><span class="mi">2</span> <span class="o">*</span> <span class="n">standard_deviation1</span><span class="o">**</span><span class="mi">2</span> <span class="o">+</span> <span class="n">w2</span><span class="o">**</span><span class="mi">2</span> <span class="o">*</span> <span class="n">standard_deviation2</span><span class="o">**</span><span class="mi">2</span> <span class="o">+</span> \ </span></span><span class="line"><span class="cl"> <span class="mi">2</span> <span class="o">*</span> <span class="n">w1</span> <span class="o">*</span> <span class="n">w2</span> <span class="o">*</span> <span class="n">standard_deviation1</span> <span class="o">*</span> <span class="n">standard_deviation2</span> <span class="o">*</span> <span class="n">correlation</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">standard_deviation</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">sqrt</span><span class="p">(</span><span class="n">variance</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">standard_deviations</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">standard_deviation</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">scatter</span><span class="p">(</span><span class="n">standard_deviations</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">],</span> <span class="n">returns</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">],</span> <span class="n">color</span><span class="o">=</span><span class="s1">&#39;#007bff&#39;</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="n">w1</span> <span class="o">==</span> <span class="mf">0.5</span> <span class="ow">and</span> <span class="n">w2</span> <span class="o">==</span> <span class="mf">0.5</span><span class="p">:</span> </span></span><span class="line"><span class="cl"> <span class="n">portfolio_50_50_standard_deviation</span> <span class="o">=</span> <span class="n">standard_deviations</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> </span></span><span class="line"><span class="cl"> <span class="n">portfolio_50_50_returns</span> <span class="o">=</span> <span class="n">returns</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">scatter</span><span class="p">(</span><span class="n">portfolio_50_50_standard_deviation</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">portfolio_50_50_returns</span><span class="p">,</span> <span class="n">marker</span><span class="o">=</span><span class="s1">&#39;x&#39;</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="s1">&#39;red&#39;</span><span class="p">,</span> <span class="n">alpha</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">s</span><span class="o">=</span><span class="mi">320</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">x_padding</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">average</span><span class="p">(</span><span class="n">standard_deviations</span><span class="p">)</span> <span class="o">/</span> <span class="mi">25</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">xlim</span><span class="p">(</span><span class="nb">min</span><span class="p">(</span><span class="n">standard_deviations</span><span class="p">)</span> <span class="o">-</span> <span class="n">x_padding</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nb">max</span><span class="p">(</span><span class="n">standard_deviations</span><span class="p">)</span> <span class="o">+</span> <span class="n">x_padding</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">y_padding</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">average</span><span class="p">(</span><span class="n">returns</span><span class="p">)</span> <span class="o">/</span> <span class="mi">25</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">ylim</span><span class="p">(</span><span class="nb">min</span><span class="p">(</span><span class="n">returns</span><span class="p">)</span> <span class="o">-</span> <span class="n">y_padding</span><span class="p">,</span> <span class="nb">max</span><span class="p">(</span><span class="n">returns</span><span class="p">)</span> <span class="o">+</span> <span class="n">y_padding</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">gca</span><span class="p">()</span><span class="o">.</span><span class="n">set_xticklabels</span><span class="p">([</span><span class="s1">&#39;</span><span class="si">{:.2f}</span><span class="s1">%&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">x</span><span class="o">*</span><span class="mi">100</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">plt</span><span class="o">.</span><span class="n">gca</span><span class="p">()</span><span class="o">.</span><span class="n">get_xticks</span><span class="p">()])</span> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">gca</span><span class="p">()</span><span class="o">.</span><span class="n">set_yticklabels</span><span class="p">([</span><span class="s1">&#39;</span><span class="si">{:.2f}</span><span class="s1">%&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">y</span><span class="o">*</span><span class="mi">100</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="n">y</span> <span class="ow">in</span> <span class="n">plt</span><span class="o">.</span><span class="n">gca</span><span class="p">()</span><span class="o">.</span><span class="n">get_yticks</span><span class="p">()])</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">title</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;Efficient Frontier (</span><span class="si">{</span><span class="n">symbol1</span><span class="o">.</span><span class="n">upper</span><span class="p">()</span><span class="si">}</span><span class="s1"> and </span><span class="si">{</span><span class="n">symbol2</span><span class="o">.</span><span class="n">upper</span><span class="p">()</span><span class="si">}</span><span class="s1">)&#39;</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">xlabel</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;Risk (</span><span class="si">{</span><span class="n">interval</span><span class="o">.</span><span class="n">value</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span><span class="o">.</span><span class="n">capitalize</span><span class="p">()</span><span class="si">}</span><span class="s1">)&#39;</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">ylabel</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;Return (</span><span class="si">{</span><span class="n">interval</span><span class="o">.</span><span class="n">value</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span><span class="o">.</span><span class="n">capitalize</span><span class="p">()</span><span class="si">}</span><span class="s1">)&#39;</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="s1">&#39;img/frontier2&#39;</span><span class="p">)</span><span class="o">.</span><span class="n">mkdir</span><span class="p">(</span><span class="n">parents</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">exist_ok</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">savefig</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;img/frontier2/</span><span class="si">{</span><span class="n">symbol1</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span><span class="si">}</span><span class="s1">-</span><span class="si">{</span><span class="n">symbol2</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span><span class="si">}</span><span class="s1">.png&#39;</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">close</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="n">show_frontier</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">2</span><span class="p">])</span> </span></span></code></pre></div><p>Now let&rsquo;s run this script in order to see the scatter plot:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">python frontier2.py ibm dis </span></span></code></pre></div><p><figure> <a href="https://bubelov.com/blog/2020/plotting-financial-data-in-python/frontier-ibm-dis_hu_a4d914542d0d11a6.webp"> <img src="https://bubelov.com/blog/2020/plotting-financial-data-in-python/frontier-ibm-dis_hu_2cff49fa88fed982.webp" alt="" /> </a> </figure></p> <p>As you can see, the 50/50 portfolio is on the bottom half of this chart. That&rsquo;s not very good because it means that there is another portfolio that has more returns with the same risks. Usually, it&rsquo;s better to ignore the bottom half of the efficient frontier and pick a portfolio from the top half that best suits your risk tolerance. Many young people prefer high returns, and it might be a good idea if your investment horizon is long enough, but the people who are close to retirement age are typically not comfortable with the idea of waiting for another 20-30 years in order to recover from a possible loss caused by holding too many high-risk assets.</p> <h3 id="conclusion-3">Conclusion</h3> <p>Now we have a script that allows us to find an optimal combination of 2 different assets. That&rsquo;s a good start, but it&rsquo;s more practical to be able to include more assets and that&rsquo;s what we&rsquo;re going to do next.</p> <h2 id="plotting-efficient-frontier-n-assets">Plotting Efficient Frontier (N assets)</h2> <p>We already have the efficient frontier script, but it has one major limitation: it does not allow us to plot more than two assets. Plotting two assets is enough to see diversification in action, but it&rsquo;s not practical to have a portfolio that consists of two assets. In this post we&rsquo;re going to extend the previous script in order to support an arbitrary number of assets.</p> <h3 id="why-diversify">Why Diversify?</h3> <p>Diversification helps to reduce portfolio volatility but to what extent? Well, it depends on the correlations between different assets, but we can safely assume that the number of assets should be greater than 2. If you decide to add another asset, the smaller the number of assets you already have in your portfolio, the better the effect of diversification. Here is the picture that helps to visualize how the number of assets affects the portfolio risk:</p> <p><figure> <a href="https://bubelov.com/blog/2020/plotting-financial-data-in-python/diversification-in-action_hu_97b70e47e1c2e550.webp"> <img src="https://bubelov.com/blog/2020/plotting-financial-data-in-python/diversification-in-action_hu_140f4e18fd5f26b2.webp" alt="" /> </a> </figure></p> <p>As you can see, one thing is clear: having two assets does not allow us to get all the benefits of diversification. There are many opinions on what number of assets is &ldquo;right&rdquo; but almost everyone agrees that two is far too low.</p> <h3 id="goal">Goal</h3> <p>We already calculated the efficient frontier for a portfolio that consists of the <code>IBM</code> and <code>DIS</code> stocks. Let&rsquo;s add one more stock to it. You can pick any stock or an index, but I&rsquo;ll go with Coca-Cola (<code>KO</code>).</p> <p>So, how do we calculate our risks and rewards?</p> <h3 id="expected-return">Expected Return</h3> <p>Here is how we can calculate the expected return on a portfolio:</p> <p>$$ E(R_p) = \sum_{i=1}^N w_i E(R_i) $$</p> <p>Where:</p> <p>\( r_p \) = expected return on a portfolio</p> <p>\( N \) = number of assets in a portfolio</p> <p>\( w_i \) = weight of an asset i in a portfolio</p> <p>\( R_i \) = expected return on asset i</p> <p>All of this is pretty simple, we just need to find the weighted average of the returns of every asset in a portfolio.</p> <h3 id="variance">Variance</h3> <p>Variance is a bit more tricky to calculate because we have to include the correlations between each pair of assets:</p> <p>$$ σ_p^2 = \sum_{i=1}^N w_i^2 σ_i^2 + \sum_{i=1}^N \sum_{j \not = i}^N w_i w_j σ_i σ_j p_{ij} $$</p> <p>Where:</p> <p>\( σ_p^2 \) = portfolio volatility</p> <p>\( w_i \) = weight of an asset i in a portfolio</p> <p>\( σ_i \) = standard deviation of an asset i</p> <p>\( p_{ij} \) = correlation of returns between the assets i and j</p> <h3 id="standard-deviation">Standard Deviation</h3> <p>Standard deviation of a portfolio is just a square root of its variance:</p> <p>$$ σ_p = (σ_p^2)^{1 \over 2} $$</p> <p>That gives us a hint about the portfolio riskiness.</p> <h3 id="implementation">Implementation</h3> <p>Let&rsquo;s create a new file and call it <code>frontier.py</code>:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python3" data-lang="python3"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">matplotlib.pyplot</span> <span class="k">as</span> <span class="nn">plt</span> </span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">sys</span> </span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pathlib</span> </span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">alpha_vantage</span> </span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">alpha_vantage</span> <span class="kn">import</span> <span class="n">Interval</span> </span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">plot_style</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">show_frontier</span><span class="p">(</span><span class="n">symbols</span><span class="p">,</span> <span class="n">interval</span><span class="o">=</span><span class="n">Interval</span><span class="o">.</span><span class="n">MONTHLY</span><span class="p">):</span> </span></span><span class="line"><span class="cl"> <span class="c1">#print(f&#39;Symbols: {symbols}&#39;)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">returns_history</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">min_length</span> <span class="o">=</span> <span class="kc">None</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="n">symbol</span> <span class="ow">in</span> <span class="n">symbols</span><span class="p">:</span> </span></span><span class="line"><span class="cl"> <span class="n">history</span> <span class="o">=</span> <span class="n">alpha_vantage</span><span class="o">.</span><span class="n">get_stock_returns_history</span><span class="p">(</span><span class="n">symbol</span><span class="p">,</span> <span class="n">interval</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="c1">#print(f&#39;Fetched {len(history)} records for symbol {symbol}&#39;)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="n">min_length</span> <span class="o">==</span> <span class="kc">None</span><span class="p">:</span> </span></span><span class="line"><span class="cl"> <span class="n">min_length</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">history</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">history</span><span class="p">)</span> <span class="o">&lt;</span> <span class="n">min_length</span><span class="p">):</span> </span></span><span class="line"><span class="cl"> <span class="n">min_length</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">history</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">returns_history</span><span class="p">[</span><span class="n">symbol</span><span class="p">]</span> <span class="o">=</span> <span class="n">history</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1">#print(f&#39;Min hisotry length = {min_length}&#39;)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="n">symbol</span> <span class="ow">in</span> <span class="n">symbols</span><span class="p">:</span> </span></span><span class="line"><span class="cl"> <span class="n">returns_history</span><span class="p">[</span><span class="n">symbol</span><span class="p">]</span> <span class="o">=</span> <span class="n">returns_history</span><span class="p">[</span><span class="n">symbol</span><span class="p">][</span><span class="o">-</span><span class="n">min_length</span><span class="p">:]</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1"># for symbol in symbols:</span> </span></span><span class="line"><span class="cl"> <span class="c1"># print(</span> </span></span><span class="line"><span class="cl"> <span class="c1"># f&#39;History for symbol {symbol} has {len(returns_history[symbol])} records&#39;)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">mean_returns</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> <span class="n">variances</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> <span class="n">standard_deviations</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="n">symbol</span> <span class="ow">in</span> <span class="n">symbols</span><span class="p">:</span> </span></span><span class="line"><span class="cl"> <span class="n">history</span> <span class="o">=</span> <span class="n">returns_history</span><span class="p">[</span><span class="n">symbol</span><span class="p">]</span> </span></span><span class="line"><span class="cl"> <span class="n">history_length</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">history</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="c1">#print(f&#39;Return history for symbol {symbol} has {history_length} records&#39;)</span> </span></span><span class="line"><span class="cl"> <span class="n">mean_returns</span><span class="p">[</span><span class="n">symbol</span><span class="p">]</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">mean</span><span class="p">(</span><span class="n">history</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">variances</span><span class="p">[</span><span class="n">symbol</span><span class="p">]</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">var</span><span class="p">(</span><span class="n">history</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">standard_deviations</span><span class="p">[</span><span class="n">symbol</span><span class="p">]</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">sqrt</span><span class="p">(</span><span class="n">variances</span><span class="p">[</span><span class="n">symbol</span><span class="p">])</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">plot_style</span><span class="o">.</span><span class="n">scatter</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">portfolio_returns</span> <span class="o">=</span> <span class="p">[]</span> </span></span><span class="line"><span class="cl"> <span class="n">portfolio_deviations</span> <span class="o">=</span> <span class="p">[]</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1_000</span><span class="p">):</span> </span></span><span class="line"><span class="cl"> <span class="n">randoms</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">random</span><span class="o">.</span><span class="n">random_sample</span><span class="p">((</span><span class="nb">len</span><span class="p">(</span><span class="n">symbols</span><span class="p">),))</span> </span></span><span class="line"><span class="cl"> <span class="n">weights</span> <span class="o">=</span> <span class="p">[</span><span class="n">random</span> <span class="o">/</span> <span class="nb">sum</span><span class="p">(</span><span class="n">randoms</span><span class="p">)</span> <span class="k">for</span> <span class="n">random</span> <span class="ow">in</span> <span class="n">randoms</span><span class="p">]</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">expected_return</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">([</span><span class="n">weights</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">*</span> <span class="n">mean_returns</span><span class="p">[</span><span class="n">symbol</span><span class="p">]</span> </span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">symbol</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">symbols</span><span class="p">)])</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">weights_times_deviations</span> <span class="o">=</span> <span class="p">[</span> </span></span><span class="line"><span class="cl"> <span class="n">weights</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">**</span><span class="mi">2</span> <span class="o">*</span> <span class="n">standard_deviations</span><span class="p">[</span><span class="n">s</span><span class="p">]</span><span class="o">**</span><span class="mi">2</span> <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">s</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">symbols</span><span class="p">)]</span> </span></span><span class="line"><span class="cl"> <span class="n">variance</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="n">weights_times_deviations</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">symbols</span><span class="p">)):</span> </span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">symbols</span><span class="p">)):</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="n">i</span> <span class="o">!=</span> <span class="n">j</span><span class="p">):</span> </span></span><span class="line"><span class="cl"> <span class="n">symbol1</span> <span class="o">=</span> <span class="n">symbols</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> </span></span><span class="line"><span class="cl"> <span class="n">symbol2</span> <span class="o">=</span> <span class="n">symbols</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> </span></span><span class="line"><span class="cl"> <span class="c1">#print(&#39;Pair = %s %s&#39; % (symbol1, symbol2))</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">weight1</span> <span class="o">=</span> <span class="n">weights</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> </span></span><span class="line"><span class="cl"> <span class="n">weight2</span> <span class="o">=</span> <span class="n">weights</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> </span></span><span class="line"><span class="cl"> <span class="c1">#print(&#39;Weights = %s %s&#39; % (weight1, weight2))</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">deviation1</span> <span class="o">=</span> <span class="n">standard_deviations</span><span class="p">[</span><span class="n">symbol1</span><span class="p">]</span> </span></span><span class="line"><span class="cl"> <span class="n">deviation2</span> <span class="o">=</span> <span class="n">standard_deviations</span><span class="p">[</span><span class="n">symbol2</span><span class="p">]</span> </span></span><span class="line"><span class="cl"> <span class="c1">#print(&#39;Deviations = %s %s&#39; % (deviation1, deviation2))</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">correlation</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">corrcoef</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">returns_history</span><span class="p">[</span><span class="n">symbol1</span><span class="p">],</span> <span class="n">returns_history</span><span class="p">[</span><span class="n">symbol2</span><span class="p">])[</span><span class="mi">0</span><span class="p">][</span><span class="mi">1</span><span class="p">]</span> </span></span><span class="line"><span class="cl"> <span class="c1">#print(&#39;Correlation = %f&#39; % correlation)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">additional_variance</span> <span class="o">=</span> <span class="n">weight1</span> <span class="o">*</span> <span class="n">weight2</span> \ </span></span><span class="line"><span class="cl"> <span class="o">*</span> <span class="n">deviation1</span> <span class="o">*</span> <span class="n">deviation2</span> \ </span></span><span class="line"><span class="cl"> <span class="o">*</span> <span class="n">correlation</span> </span></span><span class="line"><span class="cl"> <span class="c1">#print(&#39;Additional variance = %f&#39; % additional_variance)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">variance</span> <span class="o">+=</span> <span class="n">additional_variance</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">standard_deviation</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">sqrt</span><span class="p">(</span><span class="n">variance</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="c1">#print(&#39;Portfolio expected return = %f&#39; % expected_return)</span> </span></span><span class="line"><span class="cl"> <span class="c1">#print(&#39;Portfolio standard deviation = %f&#39; % standard_deviation)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">scatter</span><span class="p">(</span><span class="n">standard_deviation</span><span class="p">,</span> <span class="n">expected_return</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="s1">&#39;#007bff&#39;</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">portfolio_returns</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">expected_return</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">portfolio_deviations</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">standard_deviation</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">x_padding</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">average</span><span class="p">(</span><span class="n">portfolio_deviations</span><span class="p">)</span> <span class="o">/</span> <span class="mi">25</span> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">xlim</span><span class="p">(</span><span class="nb">min</span><span class="p">(</span><span class="n">portfolio_deviations</span><span class="p">)</span> <span class="o">-</span> <span class="n">x_padding</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nb">max</span><span class="p">(</span><span class="n">portfolio_deviations</span><span class="p">)</span> <span class="o">+</span> <span class="n">x_padding</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">y_padding</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">average</span><span class="p">(</span><span class="n">portfolio_returns</span><span class="p">)</span> <span class="o">/</span> <span class="mi">25</span> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">ylim</span><span class="p">(</span><span class="nb">min</span><span class="p">(</span><span class="n">portfolio_returns</span><span class="p">)</span> <span class="o">-</span> <span class="n">y_padding</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="nb">max</span><span class="p">(</span><span class="n">portfolio_returns</span><span class="p">)</span> <span class="o">+</span> <span class="n">y_padding</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">gca</span><span class="p">()</span><span class="o">.</span><span class="n">set_xticklabels</span><span class="p">([</span><span class="s1">&#39;</span><span class="si">{:.2f}</span><span class="s1">%&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">x</span><span class="o">*</span><span class="mi">100</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">plt</span><span class="o">.</span><span class="n">gca</span><span class="p">()</span><span class="o">.</span><span class="n">get_xticks</span><span class="p">()])</span> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">gca</span><span class="p">()</span><span class="o">.</span><span class="n">set_yticklabels</span><span class="p">([</span><span class="s1">&#39;</span><span class="si">{:.2f}</span><span class="s1">%&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">y</span><span class="o">*</span><span class="mi">100</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="n">y</span> <span class="ow">in</span> <span class="n">plt</span><span class="o">.</span><span class="n">gca</span><span class="p">()</span><span class="o">.</span><span class="n">get_yticks</span><span class="p">()])</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">title</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;Efficient Frontier </span><span class="si">{</span><span class="nb">list</span><span class="p">(</span><span class="n">s</span><span class="o">.</span><span class="n">upper</span><span class="p">()</span> <span class="k">for</span> <span class="n">s</span> <span class="ow">in</span> <span class="n">symbols</span><span class="p">)</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">xlabel</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;Risk (</span><span class="si">{</span><span class="n">interval</span><span class="o">.</span><span class="n">value</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span><span class="o">.</span><span class="n">capitalize</span><span class="p">()</span><span class="si">}</span><span class="s1">)&#39;</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">ylabel</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;Return (</span><span class="si">{</span><span class="n">interval</span><span class="o">.</span><span class="n">value</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span><span class="o">.</span><span class="n">capitalize</span><span class="p">()</span><span class="si">}</span><span class="s1">)&#39;</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="s1">&#39;img/frontier&#39;</span><span class="p">)</span><span class="o">.</span><span class="n">mkdir</span><span class="p">(</span><span class="n">parents</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">exist_ok</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">savefig</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;img/frontier/frontier.png&#39;</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">plt</span><span class="o">.</span><span class="n">close</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="n">show_frontier</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">:])</span> </span></span></code></pre></div><h3 id="testing-2">Testing</h3> <p>Now, let&rsquo;s run our new script in order to see the efficient frontier:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">python frontier.py ibm dis ko </span></span></code></pre></div><p>You should see the following image:</p> <p><figure> <a href="https://bubelov.com/blog/2020/plotting-financial-data-in-python/frontier-ibm-dis-ko_hu_9777bef57be1d019.webp"> <img src="https://bubelov.com/blog/2020/plotting-financial-data-in-python/frontier-ibm-dis-ko_hu_24761cd76295d4b7.webp" alt="" /> </a> </figure></p> <h3 id="conclusion-4">Conclusion</h3> <p>Now we are able to plot the efficient frontier based on an arbitrary number of assets. Please note that nothing is &ldquo;for sure&rdquo; in the world of investing and this model has a lot of limitations, although it&rsquo;s probably one of the best models that are currently available. Our expected return is based purely on the past performance which might not be an accurate assumption about the future.</p> <p>Another thing to consider is the limit of diversification. The benefits of having more assets tend to wear off with each new asset added to your portfolio. There is a huge difference between the 2-asset and 10-asset portfolios but there might be no gain in having 200 assets, especially if you take into account all the transaction costs of re-balancing your portfolio.</p> Kotlin Native https://bubelov.com/blog/2020/kotlin-native/ Thu, 20 Feb 2020 00:00:00 +0000 https://bubelov.com/blog/2020/kotlin-native/ <p>I&rsquo;ve been using Java for many years and there is nothing wrong with using it for most of the use cases. The problem is, I work on Android apps quite a lot, and it&rsquo;s a very fragmented platform. Developers are forced to use ancient Java versions in order to be compatible with as many Android devices as possible. Kotlin is a kind of &ldquo;hack&rdquo; that allows developers to use many productive language features that simply aren&rsquo;t available if you use Java.</p> <p>Another noteworthy thing about Kotlin is its native support for coroutines which makes front-end development much easier. Concurrency is hard and Java concurrency is not an exception. There are alternative solutions that allow developers to avoid using native concurrency API, but they usually destroy code readability by turning codebases into a hacky mess. In my opinion, coroutines allow developers to produce concise, easy to read code that can be read top-down, like a book. I believe that&rsquo;s a big advantage.</p> <p>Those two selling points are enough to convince me to use Kotlin, at least on Android. Interestingly, Kotlin ambition seem to be much bigger than that. <a href="https://kotlinlang.org/docs/reference/native-overview.html">Kotlin Native</a>, another interesting Kotlin initiative, had started to get some traction lately. I wouldn&rsquo;t speculate much about this, but I generally like the idea of sharing the &ldquo;core logic&rdquo; but doing UI via each platform&rsquo;s native toolkit. Many companies tried to go cross-platform and the results were unsatisfying (<a href="https://blogs.dropbox.com/tech/2019/08/the-not-so-hidden-cost-of-sharing-code-between-ios-and-android/">DropBox</a>, <a href="https://medium.com/airbnb-engineering/react-native-at-airbnb-f95aa460be1c">AirBnB</a>, etc). The main reason, in my opinion, is a poor choice of language and attempting to create a cross-platform UI (a la Silverlight many years ago).</p> <p><a href="https://github.com/touchlab/KaMPKit">Here</a> is the example of a hybrid architecture that I find interesting, although it&rsquo;s probably not production grade yet.</p> How to Make a Snake Game in Kotlin https://bubelov.com/blog/2020/snake-kotlin/ Sat, 15 Feb 2020 00:00:00 +0000 https://bubelov.com/blog/2020/snake-kotlin/ <p>Learn the basic concepts of game development and create your own Snake clone by following this step-by-step tutorial.</p> <p><figure> <a href="https://bubelov.com/blog/2020/snake-kotlin/thumb_hu_ccb9f51a16ad84c2.webp"> <img src="https://bubelov.com/blog/2020/snake-kotlin/thumb_hu_e8744240d875b3a3.webp" alt="" /> </a> <figcaption>Illustration by <a href="https://unsplash.com/@pawelkadysz">Pawel Kadysz</a></figcaption> </figure></p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#preface">Preface</a></li> <li><a href="#art-form-or-entertainment">Art Form or Entertainment?</a></li> <li><a href="#common-issues">Common Issues</a> <ul> <li><a href="#its-not-about-tools">It&rsquo;s Not About Tools</a></li> <li><a href="#time-is-an-important-factor">Time is an Important Factor</a></li> <li><a href="#make-it-look-good-enough">Make it Look Good Enough</a></li> </ul> </li> <li><a href="#engine">Engine</a></li> <li><a href="#source-code">Source Code</a></li> <li><a href="#creating-a-scene">Creating a Scene</a> <ul> <li><a href="#scene-class">Scene Class</a></li> <li><a href="#how-does-it-work">How Does It Work?</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </li> <li><a href="#handling-input-events">Handling Input Events</a> <ul> <li><a href="#input-class">Input Class</a></li> <li><a href="#how-does-it-work-1">How Does It Work?</a></li> <li><a href="#why-synchronize">Why Synchronize?</a></li> <li><a href="#conclusion-1">Conclusion</a></li> </ul> </li> <li><a href="#game-loop">Game Loop</a> <ul> <li><a href="#implementation">Implementation</a></li> <li><a href="#how-does-it-work-2">How Does It Work?</a></li> <li><a href="#in-the-loop">In The Loop</a></li> <li><a href="#conclusion-2">Conclusion</a></li> </ul> </li> <li><a href="#game-factory">Game Factory</a> <ul> <li><a href="#how-does-it-work-3">How Does It Work?</a></li> <li><a href="#launching-a-game">Launching a Game</a></li> <li><a href="#conclusion-3">Conclusion</a></li> </ul> </li> <li><a href="#main-menu">Main Menu</a> <ul> <li><a href="#properties">Properties</a></li> <li><a href="#update">Update</a></li> <li><a href="#draw">Draw</a></li> <li><a href="#testing">Testing</a></li> <li><a href="#conclusion-4">Conclusion</a></li> </ul> </li> <li><a href="#model">Model</a> <ul> <li><a href="#snake-model">Snake Model</a></li> <li><a href="#apple-model">Apple Model</a></li> <li><a href="#conclusion-5">Conclusion</a></li> </ul> </li> <li><a href="#game-scene">Game Scene</a> <ul> <li><a href="#movement">Movement</a></li> <li><a href="#game-scene-1">Game Scene</a></li> <li><a href="#running-the-game">Running The Game</a></li> <li><a href="#conclusion-6">Conclusion</a></li> </ul> </li> <li><a href="#finalizing-the-game">Finalizing The Game</a> <ul> <li><a href="#game-over-scene">Game Over Scene</a></li> <li><a href="#conclusion-7">Conclusion</a></li> </ul> </li> </ul> </nav> </div> <h2 id="preface">Preface</h2> <p>Would you like to be able to create an immersive world from scratch and let other people explore it? It&rsquo;s truly a magical power and I wanted to acquire this power since my early childhood. In fact, that&rsquo;s why I chose to study computer science.</p> <p>Game development is one of the most fulfilling things I experienced in my life. Games have no pre-existing rules or limits, the only limit is your own imagination. You&rsquo;re free to create whole worlds from scratch and they&rsquo;ll look and behave exactly as you want them to. It gives you a powerful way to express your ideas and share them with other people.</p> <p>I&rsquo;m not a professional game developer. Why? It turned out there are many other useful things computers can do and, unfortunately, <a href="https://www.nytimes.com/2019/04/04/opinion/video-games-layoffs-union.html">making video games isn&rsquo;t a dream job</a>. I&rsquo;ve made a lot of games during my student years and it helped me to land my first programming job but making games is just one of my hobbies now. This means I&rsquo;m not the best person to give anyone advice on how to become a professional game developer. This guide is aimed at hobbyists, like myself, who just want to learn the basic concepts of game development using Kotlin.</p> <h2 id="art-form-or-entertainment">Art Form or Entertainment?</h2> <p>In my opinion, game development is a form of art. Games can tell you an interesting story, just like a good book does, but you can actually see this story exactly as the author wanted you to see it. Movies are better at visuals but they&rsquo;re linear so you can only be an observer who have no influence on what&rsquo;s happening. It can be a good thing, but it&rsquo;s limiting. Also, the movies have a fixed pace and it’s one of the reasons why some books are much better than movies despite the lack of visuals: movies are often artificially shortened to fit the movie theater standards or artificially extended to show you more ads.</p> <h2 id="common-issues">Common Issues</h2> <p>The most common issue that an indie game developer might face is the lack of motivation to finish a game. Here are several tips on how to stay motivated:</p> <h3 id="its-not-about-tools">It&rsquo;s Not About Tools</h3> <p>It’s impossible to master everything, learning every aspect of game development can take years but it’s not necessary to start writing games as a hobby. Many beginners try to use low-level libraries such as OpenGL, DirectX or Vulkan in their first games. That’s what a professional game developer should know how to use but in most of the cases the Canvas API would be a better choice since it’s much simpler to use. You can save a lot of time and nerves if you select the right tools.</p> <h3 id="time-is-an-important-factor">Time is an Important Factor</h3> <p>A lot of people try to write their own MMORPG or think they can match the top games in the industry in terms of the features. Let’s make it clear: even if you have the required skills and motivation, no one can make such a game in reasonable time. It takes hundreds of people and a few years, sometimes more than a decade to produce a typical AAA game. As an indie game developer or a small team, you don’t have a lot of time and resources but there are advantages in being small that you can utilize. Although it’s nearly impossible for you to create a giant game world and tons of beautiful graphics, you are able to experiment more with a gameplay. Big companies are too worried for their sales and it limits their ability to experiment with a gameplay. If you have no investors to please, that actually gives you a big advantage.</p> <h3 id="make-it-look-good-enough">Make it Look Good Enough</h3> <p>Graphics is an important part of modern games. It’s cool if you can write a clean and beautiful code but players won’t see it. If your game looks bad it can discourage the players and even the authors themselves. It’s much more fun to work on something that looks nice and clean. A typical indie game doesn&rsquo;t require a lot of graphics but graphics is very important to keep you motivated and make your game more enjoyable.</p> <h2 id="engine">Engine</h2> <p>We won’t use any specific game engine, it’s more fun to create a game engine from scratch and it will also help us to better understand how video games work under the hood. We&rsquo;re going to create a Snake game clone because it’s relatively easy to implement which makes it a good game to start with.</p> <h2 id="source-code">Source Code</h2> <p>You can check the full source code <a href="https://github.com/bubelov/snake-kotlin">here</a>. It may help you to understand project confuguration and see the end result that you should expect if you&rsquo;ll follow this tutorial.</p> <h2 id="creating-a-scene">Creating a Scene</h2> <p>Every game is different but, luckily for us, many video games share a common set of components and patterns. There are things that may be specific to a certain genre but on a high enough level all games are more or less the same and they all made of the same components.</p> <p>Some of those components are:</p> <ul> <li> <p><code>Screen</code> - player has to be able to see what’s going on in the game and using a screen is a convenient way to provide visual feedback. It can be a TV screen, computer screen, mobile screen or even a VR headset. Technically, there are two screens in a VR headset but we don’t have to worry about that, most of the required adjustments are handled automatically by the headset hardware.</p> </li> <li> <p><code>Controller</code> - it’s the device that can be used to interact with video games. Many devices can be used as controllers: keyboards, computer mice, joysticks and a variety of more exotic devices such as steering wheels or fighter jet controllers.</p> </li> <li> <p><code>Game Objects</code> - anything that exists in a game is a game object. Some objects are static, such as the walls, and some are more dynamic, such as the player’s game character or his friends and enemies.</p> </li> </ul> <h3 id="scene-class">Scene Class</h3> <p>Let’s start with implementing a screen component. A typical desktop app consists of several windows and every window has its own purpose and separating concerns is usually beneficial for the app users and the developers alike. App users may have a better focus and feel less overwhelmed since there is a separate window for each specific task and the developers can split the application logic between those screens which makes it easier to maintain the code base.</p> <p>Video games don’t have a window system by default, but it doesn’t mean we can’t implement it. I think we shouldn’t use the name ‘window’ when we’re making a game. “Level” seems to be a good name, many games do look like a series of levels but, in my opinion, this name is not abstract enough. For instance, a main menu or a settings screen are visually separated from the rest of the game and it’s confusing to treat them as “levels” but they also have a lot in common with the rest of the screens: they usually display something and process the input events from the game controllers. So let’s call it a scene, it will be easier to understand that any kind of action can happen here as long as it has something to show to the player. Obviously, we don’t want the player to stare at an empty scene, it’s no fun at all.</p> <p>Here is our <code>Scene</code> class:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">import</span> <span class="nn">java.awt.Graphics2D</span> </span></span><span class="line"><span class="cl"><span class="k">import</span> <span class="nn">java.util.concurrent.TimeUnit</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">abstract</span> <span class="k">class</span> <span class="nc">Scene</span><span class="p">(</span><span class="k">val</span> <span class="py">game</span><span class="p">:</span> <span class="n">Game</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">abstract</span> <span class="k">fun</span> <span class="nf">update</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">timePassed</span><span class="p">:</span> <span class="n">Long</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">timeUnits</span><span class="p">:</span> <span class="n">TimeUnit</span> <span class="p">=</span> <span class="nc">TimeUnit</span><span class="p">.</span><span class="n">NANOSECONDS</span> </span></span><span class="line"><span class="cl"> <span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">abstract</span> <span class="k">fun</span> <span class="nf">draw</span><span class="p">(</span><span class="n">graphics</span><span class="p">:</span> <span class="n">Graphics2D</span><span class="p">)</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><h3 id="how-does-it-work">How Does It Work?</h3> <p>The <code>update</code> method is supposed to be called each time we want the scene to update it’s state but, in order to perform the update, the scene needs to know how much time has passed since the last call. That’s why we require the caller to provide us with the <code>timePassed</code> value. For instance if we have a moving car on the scene there is no way to tell how far it should move unless we know both its speed and the amount of time that has passed since the last update. The <code>update</code> and <code>draw</code> methods should be called at least 30 times per second to make sure that all the game objects will move smoothly. That’s why if you try to launch a resource intensive game on the old hardware it would look like a slide show. Some computers might be unable to update and draw the game scenes fast enough to make the game look &ldquo;realtime&rdquo;.</p> <p>The only purpose of the <code>update</code> method is to sync &ldquo;game time&rdquo; with &ldquo;real time&rdquo;. We don&rsquo;t care what a particular game does with this information, we just need to tell the game how much time has passed since the last call.</p> <p>Updating game state is important, but we still need a way to display it in order to make it visible to a player. That’s why we have introduced the <code>draw</code> method, and it supposed to be called immediately after <code>update</code>.</p> <h3 id="conclusion">Conclusion</h3> <p>Now we have our way to separate our games into a set of scenes, but it’s only one of the core game components we need. Next, we&rsquo;re going to make a component which will handle the data from the input devices. It’s fun to see the objects move but it’s even more fun to be able to control them.</p> <h2 id="handling-input-events">Handling Input Events</h2> <p>Controllers are devices that can register players&rsquo; actions, and we want our players to be able to alter the course of a game. In more technical terms it means that every game has a state and the game controllers are devices that can be used to change that state.</p> <p>There are lots of different input devices: typical console games tend to rely on game-pads, flight or racing simulators can support their own unique controllers, but we’re writing a PC game, so we’ll stick with the keyboard: the most popular input device used to interact with personal computers.</p> <p>It makes sense to also support the computer mouse and trackpad, but we don&rsquo;t really need those devices to control a snake, so we can do it later. It will also make the code a bit more simple, and it&rsquo;s always better not to add any unnecessary complexity.</p> <h3 id="input-class">Input Class</h3> <p>Let&rsquo;s add a new class and call it <code>Input</code></p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">import</span> <span class="nn">java.awt.event.KeyEvent</span> </span></span><span class="line"><span class="cl"><span class="k">import</span> <span class="nn">java.awt.event.KeyListener</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Input</span> <span class="p">:</span> <span class="n">KeyListener</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">private</span> <span class="k">val</span> <span class="py">events</span> <span class="p">=</span> <span class="n">mutableListOf</span><span class="p">&lt;</span><span class="n">Event</span><span class="p">&gt;()</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">override</span> <span class="k">fun</span> <span class="nf">keyTyped</span><span class="p">(</span><span class="n">event</span><span class="p">:</span> <span class="n">KeyEvent</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="c1">// We&#39;re not interested in this kind of events </span></span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">override</span> <span class="k">fun</span> <span class="nf">keyPressed</span><span class="p">(</span><span class="n">event</span><span class="p">:</span> <span class="n">KeyEvent</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">synchronized</span><span class="p">(</span><span class="k">this</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">events</span> <span class="o">+=</span> <span class="nc">Event</span><span class="p">.</span><span class="n">KeyPressed</span><span class="p">(</span><span class="n">event</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">override</span> <span class="k">fun</span> <span class="nf">keyReleased</span><span class="p">(</span><span class="n">event</span><span class="p">:</span> <span class="n">KeyEvent</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">synchronized</span><span class="p">(</span><span class="k">this</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">events</span> <span class="o">+=</span> <span class="nc">Event</span><span class="p">.</span><span class="n">KeyReleased</span><span class="p">(</span><span class="n">event</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">fun</span> <span class="nf">consumeEvents</span><span class="p">():</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">Event</span><span class="p">&gt;</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">synchronized</span><span class="p">(</span><span class="k">this</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">consumedEvents</span> <span class="p">=</span> <span class="n">events</span><span class="p">.</span><span class="n">toList</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> <span class="n">events</span><span class="p">.</span><span class="n">clear</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="n">consumedEvents</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">sealed</span> <span class="k">class</span> <span class="nc">Event</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">data</span> <span class="k">class</span> <span class="nc">KeyPressed</span><span class="p">(</span><span class="k">val</span> <span class="py">data</span><span class="p">:</span> <span class="n">KeyEvent</span><span class="p">)</span> <span class="p">:</span> <span class="n">Event</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> <span class="k">data</span> <span class="k">class</span> <span class="nc">KeyReleased</span><span class="p">(</span><span class="k">val</span> <span class="py">data</span><span class="p">:</span> <span class="n">KeyEvent</span><span class="p">)</span> <span class="p">:</span> <span class="n">Event</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><h3 id="how-does-it-work-1">How Does It Work?</h3> <p>Let’s take a closer look at our <code>Input</code> class. It implements the <code>KeyListener</code> interface which allows it to be notified of any keyboard events that happen inside the game window.</p> <p>There are 3 methods we have to listen to in order to implement the <code>KeyListener</code> interface:</p> <ul> <li><code>keyTyped</code> - we don&rsquo;t need those events, so we will ignore them</li> <li><code>keyPressed</code> - this method is being called each time our player presses a button on a keyboard. We want to know about that because we need to save this event for later processing</li> <li><code>keyReleased</code> - the player released a button, it can be valuable, so we need to save it too. Sometimes we want to do something while a certain button is pressed, so we need to know when to start as well as when to stop such actions (think of a gas pedal or something like that)</li> </ul> <p>Note that we just store those events for later use, so we expect some other component to actually react to them. I&rsquo;ve called it event consumption because once the external component reads the available events - they are gone. It&rsquo;s more straightforward because it helps us to avoid processing the same event twice because it will never return twice, but it also assumes that we have a single consumer, otherwise such a model wouldn&rsquo;t work in a predictable way.</p> <h3 id="why-synchronize">Why Synchronize?</h3> <p>Probably you&rsquo;ve noticed that all the code that touches the event collection is placed inside the <code>synchronized</code> blocks. The reason is that our game loop is using its own thread but our game window is a part of the Swing library which uses a different thread so we have to be extra cautious about that. Concurrent modification of a collection is a thing we would like to avoid since it will crash our game from time to time or introduce a lot of weird and hard to trace side effects.</p> <p>So what exactly are the <code>synchronized</code> blocks used for? They guarantee that the code inside them will never be used by more than one thread at the same time. We can also use this keyword for several methods inside a class and that would guarantee that only one thread can access any of those methods at any given moment. This of course leads to some performance drawbacks, but it will guarantee that our class will work properly in a concurrent environment, and we can actually use more advanced synchronization techniques, but we won’t be using them in this tutorial to keep the code as simple as possible.</p> <h3 id="conclusion-1">Conclusion</h3> <p>Now we have the <code>Scene</code> and the <code>Input</code> classes done. In the next piece we’ll create the key element of our mini engine - the <code>Game</code> class. It will utilize and tie together the code we had created earlier.</p> <h2 id="game-loop">Game Loop</h2> <p>Every game has a game loop. It is a simple loop that should be familiar to every programmer. We can think of a game as a sequence of static images changing fast enough to create an illusion of motion. The purpose of the game loop is to keep generating new frames as long as the loop lasts and, as with any loop, it shouldn&rsquo;t last forever, so we must have some way of breaking that loop, usually when a player decides to quit the game.</p> <h3 id="implementation">Implementation</h3> <p>We&rsquo;re going to create a class called <code>Game</code> which will wrap the game loop, and it will also contain all the crucial game components such as an input handling module and the current scene.</p> <p>This class will have two methods:</p> <ul> <li><code>play</code> - this method will activate the game loop, so it can start producing new frames.</li> <li><code>pause</code> - this method will move the game to the inactive state. The game loop should be stopped which means the game should stop producing new frames.</li> </ul> <p>Here is the code:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">import</span> <span class="nn">kotlinx.coroutines.*</span> </span></span><span class="line"><span class="cl"><span class="k">import</span> <span class="nn">kotlinx.coroutines.swing.Swing</span> </span></span><span class="line"><span class="cl"><span class="k">import</span> <span class="nn">java.awt.Canvas</span> </span></span><span class="line"><span class="cl"><span class="k">import</span> <span class="nn">java.awt.Dimension</span> </span></span><span class="line"><span class="cl"><span class="k">import</span> <span class="nn">java.awt.Graphics2D</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Game</span><span class="p">(</span><span class="k">val</span> <span class="py">screenSize</span><span class="p">:</span> <span class="n">Dimension</span><span class="p">)</span> <span class="p">:</span> <span class="n">Canvas</span><span class="p">()</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">var</span> <span class="py">scene</span><span class="p">:</span> <span class="n">Scene</span><span class="p">?</span> <span class="p">=</span> <span class="k">null</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">input</span> <span class="p">=</span> <span class="n">Input</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">private</span> <span class="k">var</span> <span class="py">gameLoop</span><span class="p">:</span> <span class="n">Job</span><span class="p">?</span> <span class="p">=</span> <span class="k">null</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">init</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">size</span> <span class="p">=</span> <span class="n">screenSize</span> </span></span><span class="line"><span class="cl"> <span class="n">addKeyListener</span><span class="p">(</span><span class="n">input</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">fun</span> <span class="nf">play</span><span class="p">()</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="n">gameLoop</span> <span class="o">!=</span> <span class="k">null</span><span class="p">)</span> <span class="k">return</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">gameLoop</span> <span class="p">=</span> <span class="nc">GlobalScope</span><span class="p">.</span><span class="n">launch</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">var</span> <span class="py">lastIterationTime</span> <span class="p">=</span> <span class="nc">System</span><span class="p">.</span><span class="n">nanoTime</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">while</span> <span class="p">(</span><span class="n">isActive</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">scene</span> <span class="p">=</span> <span class="n">scene</span> <span class="o">?:</span> <span class="k">continue</span> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">now</span> <span class="p">=</span> <span class="nc">System</span><span class="p">.</span><span class="n">nanoTime</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">timePassed</span> <span class="p">=</span> <span class="n">now</span> <span class="p">-</span> <span class="n">lastIterationTime</span> </span></span><span class="line"><span class="cl"> <span class="n">lastIterationTime</span> <span class="p">=</span> <span class="n">now</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">scene</span><span class="p">.</span><span class="n">update</span><span class="p">(</span><span class="n">timePassed</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">withContext</span><span class="p">(</span><span class="nc">Dispatchers</span><span class="p">.</span><span class="n">Swing</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">scene</span><span class="p">.</span><span class="n">draw</span><span class="p">(</span><span class="n">bufferStrategy</span><span class="p">.</span><span class="n">drawGraphics</span> <span class="k">as</span> <span class="n">Graphics2D</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">bufferStrategy</span><span class="p">.</span><span class="n">show</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">fun</span> <span class="nf">pause</span><span class="p">()</span> <span class="p">=</span> <span class="n">runBlocking</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">gameLoop</span><span class="o">?.</span><span class="n">cancel</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> <span class="n">gameLoop</span><span class="o">?.</span><span class="n">join</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> <span class="n">gameLoop</span> <span class="p">=</span> <span class="k">null</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><h3 id="how-does-it-work-2">How Does It Work?</h3> <p>As you can see, the <code>Game</code> class extends the <code>Canvas</code> which is the part of the <code>AWT</code> (Abstract Window Toolkit) library. We can think of the <code>Canvas</code> as the empty drawing space that we&rsquo;ll be using to draw our game scenes on and because our <code>Game</code> class is also a <code>Canvas</code> we can easily place it inside any <code>AWT</code> window.</p> <p>The <code>Game</code> constructor takes a single parameter: <code>screenSize</code>, it should state how much space (in pixels) our game wants to occupy. The <code>Game</code> class also has references to the current scene, the input module and the game loop. The <code>play</code> and <code>pause</code> methods are used to control the game lifecycle.</p> <p>The most interesting part of this class is the content of the game loop, so let&rsquo;s examine it in more detail:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="n">gameLoop</span> <span class="p">=</span> <span class="nc">GlobalScope</span><span class="p">.</span><span class="n">launch</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">var</span> <span class="py">lastIterationTime</span> <span class="p">=</span> <span class="nc">System</span><span class="p">.</span><span class="n">nanoTime</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">while</span> <span class="p">(</span><span class="n">isActive</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">scene</span> <span class="p">=</span> <span class="n">scene</span> <span class="o">?:</span> <span class="k">continue</span> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">now</span> <span class="p">=</span> <span class="nc">System</span><span class="p">.</span><span class="n">nanoTime</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">timePassed</span> <span class="p">=</span> <span class="n">now</span> <span class="p">-</span> <span class="n">lastIterationTime</span> </span></span><span class="line"><span class="cl"> <span class="n">lastIterationTime</span> <span class="p">=</span> <span class="n">now</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">scene</span><span class="p">.</span><span class="n">update</span><span class="p">(</span><span class="n">timePassed</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">withContext</span><span class="p">(</span><span class="nc">Dispatchers</span><span class="p">.</span><span class="n">Swing</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">scene</span><span class="p">.</span><span class="n">draw</span><span class="p">(</span><span class="n">bufferStrategy</span><span class="p">.</span><span class="n">drawGraphics</span> <span class="k">as</span> <span class="n">Graphics2D</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">bufferStrategy</span><span class="p">.</span><span class="n">show</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>Before we go into the <code>while</code> loop, we have to initialize the variable called <code>lastIterationTime</code>. This variable holds the time of the previous iteration, and we need to know it in order to find out how much time has passed since the last frame was rendered.</p> <p>The loop itself will run until the enclosing coroutine is active. Calling the <code>pause</code> method will make this coroutine inactive, so it will stop the loop so the code inside it will stop repeating.</p> <h3 id="in-the-loop">In The Loop</h3> <p>The first thing that the loop does is calculating how much time has passed since the last iteration, and it can vary from computer to computer. The <code>timePassed</code> value will be larger on slower PCs and lower on the fastest ones. Obviously, the lower, the better but players would hardly notice any difference if the game loop can do at least 30 iterations per second. It would even make sense to cap the maximum amount of frames at 60 per second to make sure we&rsquo;re not wasting more processing power than we actually need to make sure the game runs smoothly.</p> <p>Now that we know how much time has passed since the previous iteration, we can call the <code>update</code> and <code>draw</code> methods to update the game state and draw that state on the screen. We can also obtain a <code>Graphics2D</code> object from our <code>Canvas</code> which can be used by our scenes to perform various drawing operations.</p> <p>And the last step our loop is supposed to do is to call the <code>BufferStrategy.show</code> method to notify other UI components that the frame is ready to be displayed.</p> <h3 id="conclusion-2">Conclusion</h3> <p>Now we have the game loop, the input module and the <code>Scene</code> class to display our frames. It&rsquo;s all tied together and managed by the <code>Game</code> class. The only thing our little framework is missing is an actual window. The game needs to live inside a window and that&rsquo;s what we&rsquo;re going to implement next.</p> <h2 id="game-factory">Game Factory</h2> <p>We already have a set of components for controlling the game loop, drawing on a screen and processing the input events so why do we need something else? There are two key things that are still missing:</p> <ol> <li>We need a place to instantiate our game</li> <li>Our game needs a window so we have to provide it</li> </ol> <p>Let&rsquo;s add the <code>GameFactory</code> class which will do those tasks for us:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">import</span> <span class="nn">java.awt.Dimension</span> </span></span><span class="line"><span class="cl"><span class="k">import</span> <span class="nn">javax.swing.WindowConstants</span> </span></span><span class="line"><span class="cl"><span class="k">import</span> <span class="nn">java.awt.BorderLayout</span> </span></span><span class="line"><span class="cl"><span class="k">import</span> <span class="nn">javax.swing.JFrame</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">object</span> <span class="nc">GameFactory</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">fun</span> <span class="nf">create</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">screenSize</span><span class="p">:</span> <span class="n">Dimension</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">windowTitle</span><span class="p">:</span> <span class="n">String</span> </span></span><span class="line"><span class="cl"> <span class="p">):</span> <span class="n">Game</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">game</span> <span class="p">=</span> <span class="n">Game</span><span class="p">(</span><span class="n">screenSize</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">JFrame</span><span class="p">().</span><span class="n">apply</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">title</span> <span class="p">=</span> <span class="n">windowTitle</span> </span></span><span class="line"><span class="cl"> <span class="n">isVisible</span> <span class="p">=</span> <span class="k">true</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">layout</span> <span class="p">=</span> <span class="n">BorderLayout</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> <span class="n">add</span><span class="p">(</span><span class="n">game</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">pack</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">defaultCloseOperation</span> <span class="p">=</span> <span class="nc">WindowConstants</span><span class="p">.</span><span class="n">EXIT_ON_CLOSE</span> </span></span><span class="line"><span class="cl"> <span class="n">setLocationRelativeTo</span><span class="p">(</span><span class="k">null</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">game</span><span class="p">.</span><span class="n">createBufferStrategy</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">game</span><span class="p">.</span><span class="n">requestFocus</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="n">game</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><h3 id="how-does-it-work-3">How Does It Work?</h3> <p>This class takes the screen size and window title as arguments. Both of them can vary from game to game, so we shouldn&rsquo;t hard-code it in order to make this factory reusable.</p> <p>Our game can&rsquo;t appear on the screen if there is no window to host it. The code above uses a <code>JFrame</code> to create the game window. We should also make sure that the game is visible and that the window is not resizable by default. The only line that seems a bit odd is <code>setLocationRelativeTo(null)</code> and it simply means that we want our game window to be placed right in the center of a computer screen.</p> <p>The last step is to create a buffer strategy and request the input focus so our game can receive the input events from a keyboard.</p> <h3 id="launching-a-game">Launching a Game</h3> <p>Let&rsquo;s create a new file and call it <code>Main.kt</code> which will serve as an entry point to our game. Here is the code:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">import</span> <span class="nn">java.awt.Dimension</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">fun</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">game</span> <span class="p">=</span> <span class="nc">GameFactory</span><span class="p">.</span><span class="n">create</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">screenSize</span> <span class="p">=</span> <span class="n">Dimension</span><span class="p">(</span><span class="mi">660</span><span class="p">,</span> <span class="mi">660</span><span class="p">),</span> </span></span><span class="line"><span class="cl"> <span class="n">windowTitle</span> <span class="p">=</span> <span class="s2">&#34;Snake&#34;</span> </span></span><span class="line"><span class="cl"> <span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">game</span><span class="p">.</span><span class="n">play</span><span class="p">()</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>Now we can launch our game engine and see if it works so feel free to do it. You should see the empty white window in the center of your screen.</p> <h3 id="conclusion-3">Conclusion</h3> <p>Our &ldquo;game engine&rdquo; is up and running and that means we can start using it. Our next goal is to create a simple scene to demonstrate how to draw on the screen and handle user input.</p> <h2 id="main-menu">Main Menu</h2> <p>Most of the games have a main menu because we might not always know what the player want to do when he or she launches a game. The player might want to start a new game, load the saved game data or modify the game settings but we will have a very simple menu in the Snake game which will have only one option: start a new game.</p> <p>The main menu is just a <code>Scene</code> so it already has the ability to draw on the screen and it can also handle user input. Let&rsquo;s see the whole code first:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">import</span> <span class="nn">java.awt.Color</span> </span></span><span class="line"><span class="cl"><span class="k">import</span> <span class="nn">java.awt.Font</span> </span></span><span class="line"><span class="cl"><span class="k">import</span> <span class="nn">java.awt.Graphics2D</span> </span></span><span class="line"><span class="cl"><span class="k">import</span> <span class="nn">java.awt.event.KeyEvent</span> </span></span><span class="line"><span class="cl"><span class="k">import</span> <span class="nn">java.util.concurrent.TimeUnit</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">MainMenuScene</span><span class="p">(</span><span class="n">game</span><span class="p">:</span> <span class="n">Game</span><span class="p">)</span> <span class="p">:</span> <span class="n">Scene</span><span class="p">(</span><span class="n">game</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">private</span> <span class="k">val</span> <span class="py">primaryFont</span> <span class="p">=</span> <span class="n">Font</span><span class="p">(</span><span class="s2">&#34;Default&#34;</span><span class="p">,</span> <span class="nc">Font</span><span class="p">.</span><span class="n">BOLD</span><span class="p">,</span> <span class="mi">30</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="k">private</span> <span class="k">val</span> <span class="py">secondaryFont</span> <span class="p">=</span> <span class="n">Font</span><span class="p">(</span><span class="s2">&#34;Default&#34;</span><span class="p">,</span> <span class="nc">Font</span><span class="p">.</span><span class="n">PLAIN</span><span class="p">,</span> <span class="mi">20</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">override</span> <span class="k">fun</span> <span class="nf">update</span><span class="p">(</span><span class="n">timePassed</span><span class="p">:</span> <span class="n">Long</span><span class="p">,</span> <span class="n">timeUnits</span><span class="p">:</span> <span class="n">TimeUnit</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">game</span><span class="p">.</span><span class="n">input</span><span class="p">.</span><span class="n">consumeEvents</span><span class="p">().</span><span class="n">forEach</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="k">it</span> <span class="k">is</span> <span class="nc">Input</span><span class="p">.</span><span class="nc">Event</span><span class="p">.</span><span class="n">KeyPressed</span> <span class="o">&amp;&amp;</span> <span class="k">it</span><span class="p">.</span><span class="k">data</span><span class="p">.</span><span class="n">keyCode</span> <span class="o">==</span> <span class="nc">KeyEvent</span><span class="p">.</span><span class="n">VK_ENTER</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">game</span><span class="p">.</span><span class="n">scene</span> <span class="p">=</span> <span class="n">GameScene</span><span class="p">(</span><span class="n">game</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">override</span> <span class="k">fun</span> <span class="nf">draw</span><span class="p">(</span><span class="n">graphics</span><span class="p">:</span> <span class="n">Graphics2D</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">graphics</span><span class="p">.</span><span class="n">apply</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">color</span> <span class="p">=</span> <span class="nc">Color</span><span class="p">.</span><span class="n">black</span> </span></span><span class="line"><span class="cl"> <span class="n">fillRect</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">game</span><span class="p">.</span><span class="n">screenSize</span><span class="p">.</span><span class="n">width</span><span class="p">,</span> <span class="n">game</span><span class="p">.</span><span class="n">screenSize</span><span class="p">.</span><span class="n">height</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">font</span> <span class="p">=</span> <span class="n">primaryFont</span> </span></span><span class="line"><span class="cl"> <span class="n">color</span> <span class="p">=</span> <span class="nc">Color</span><span class="p">.</span><span class="n">white</span> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">name</span> <span class="p">=</span> <span class="s2">&#34;Snake&#34;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">drawString</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">name</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">game</span><span class="p">.</span><span class="n">screenSize</span><span class="p">.</span><span class="n">width</span> <span class="p">/</span> <span class="mi">2</span> <span class="p">-</span> <span class="n">fontMetrics</span><span class="p">.</span><span class="n">stringWidth</span><span class="p">(</span><span class="n">name</span><span class="p">)</span> <span class="p">/</span> <span class="mi">2</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">game</span><span class="p">.</span><span class="n">screenSize</span><span class="p">.</span><span class="n">height</span> <span class="p">/</span> <span class="mi">2</span> <span class="p">-</span> <span class="mi">50</span> </span></span><span class="line"><span class="cl"> <span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">font</span> <span class="p">=</span> <span class="n">secondaryFont</span> </span></span><span class="line"><span class="cl"> <span class="n">color</span> <span class="p">=</span> <span class="nc">Color</span><span class="p">.</span><span class="n">gray</span> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">message</span> <span class="p">=</span> <span class="s2">&#34;Press Enter to continue&#34;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">drawString</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">message</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">game</span><span class="p">.</span><span class="n">screenSize</span><span class="p">.</span><span class="n">width</span> <span class="p">/</span> <span class="mi">2</span> <span class="p">-</span> <span class="n">fontMetrics</span><span class="p">.</span><span class="n">stringWidth</span><span class="p">(</span><span class="n">message</span><span class="p">)</span> <span class="p">/</span> <span class="mi">2</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">game</span><span class="p">.</span><span class="n">screenSize</span><span class="p">.</span><span class="n">height</span> <span class="p">/</span> <span class="mi">2</span> <span class="p">+</span> <span class="mi">50</span> </span></span><span class="line"><span class="cl"> <span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><h3 id="properties">Properties</h3> <p>Let&rsquo;s examine all the properties that are declared in this class:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">private</span> <span class="k">val</span> <span class="py">primaryFont</span> <span class="p">=</span> <span class="n">Font</span><span class="p">(</span><span class="s2">&#34;Default&#34;</span><span class="p">,</span> <span class="nc">Font</span><span class="p">.</span><span class="n">BOLD</span><span class="p">,</span> <span class="mi">30</span><span class="p">)</span> </span></span><span class="line"><span class="cl"><span class="k">private</span> <span class="k">val</span> <span class="py">secondaryFont</span> <span class="p">=</span> <span class="n">Font</span><span class="p">(</span><span class="s2">&#34;Default&#34;</span><span class="p">,</span> <span class="nc">Font</span><span class="p">.</span><span class="n">PLAIN</span><span class="p">,</span> <span class="mi">20</span><span class="p">)</span> </span></span></code></pre></div><p>Both of those properties have the same type: <code>Font</code>. We could have declared them in the <code>draw</code> method because it&rsquo;s the only method that uses them, but we have to keep in mind that the <code>draw</code> method is usually called multiple times per second, so it&rsquo;s extremely wasteful to initialize complex objects each time this method is called.</p> <h3 id="update">Update</h3> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">override</span> <span class="k">fun</span> <span class="nf">update</span><span class="p">(</span><span class="n">timePassed</span><span class="p">:</span> <span class="n">Long</span><span class="p">,</span> <span class="n">timeUnits</span><span class="p">:</span> <span class="n">TimeUnit</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">game</span><span class="p">.</span><span class="n">input</span><span class="p">.</span><span class="n">consumeEvents</span><span class="p">().</span><span class="n">forEach</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="k">it</span> <span class="k">is</span> <span class="nc">Input</span><span class="p">.</span><span class="nc">Event</span><span class="p">.</span><span class="n">KeyPressed</span> <span class="o">&amp;&amp;</span> <span class="k">it</span><span class="p">.</span><span class="k">data</span><span class="p">.</span><span class="n">keyCode</span> <span class="o">==</span> <span class="nc">KeyEvent</span><span class="p">.</span><span class="n">VK_ENTER</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">game</span><span class="p">.</span><span class="n">scene</span> <span class="p">=</span> <span class="n">GameScene</span><span class="p">(</span><span class="n">game</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>This method is quite straightforward because the only thing we have to do is to scan through all the keys that were pressed and check if any of them is the enter key. In case the enter key was pressed we should switch to the next screen. You can create an empty scene and call it <code>GameScene</code> to avoid compilation errors.</p> <h3 id="draw">Draw</h3> <p>Let&rsquo;s take a look at the <code>draw</code> method:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">override</span> <span class="k">fun</span> <span class="nf">draw</span><span class="p">(</span><span class="n">graphics</span><span class="p">:</span> <span class="n">Graphics2D</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">graphics</span><span class="p">.</span><span class="n">apply</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">color</span> <span class="p">=</span> <span class="nc">Color</span><span class="p">.</span><span class="n">black</span> </span></span><span class="line"><span class="cl"> <span class="n">fillRect</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">game</span><span class="p">.</span><span class="n">screenSize</span><span class="p">.</span><span class="n">width</span><span class="p">,</span> <span class="n">game</span><span class="p">.</span><span class="n">screenSize</span><span class="p">.</span><span class="n">height</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">font</span> <span class="p">=</span> <span class="n">primaryFont</span> </span></span><span class="line"><span class="cl"> <span class="n">color</span> <span class="p">=</span> <span class="nc">Color</span><span class="p">.</span><span class="n">white</span> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">name</span> <span class="p">=</span> <span class="s2">&#34;Snake&#34;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">drawString</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">name</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">game</span><span class="p">.</span><span class="n">screenSize</span><span class="p">.</span><span class="n">width</span> <span class="p">/</span> <span class="mi">2</span> <span class="p">-</span> <span class="n">fontMetrics</span><span class="p">.</span><span class="n">stringWidth</span><span class="p">(</span><span class="n">name</span><span class="p">)</span> <span class="p">/</span> <span class="mi">2</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">game</span><span class="p">.</span><span class="n">screenSize</span><span class="p">.</span><span class="n">height</span> <span class="p">/</span> <span class="mi">2</span> <span class="p">-</span> <span class="mi">50</span> </span></span><span class="line"><span class="cl"> <span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">font</span> <span class="p">=</span> <span class="n">secondaryFont</span> </span></span><span class="line"><span class="cl"> <span class="n">color</span> <span class="p">=</span> <span class="nc">Color</span><span class="p">.</span><span class="n">gray</span> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">message</span> <span class="p">=</span> <span class="s2">&#34;Press Enter to continue&#34;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">drawString</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">message</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">game</span><span class="p">.</span><span class="n">screenSize</span><span class="p">.</span><span class="n">width</span> <span class="p">/</span> <span class="mi">2</span> <span class="p">-</span> <span class="n">fontMetrics</span><span class="p">.</span><span class="n">stringWidth</span><span class="p">(</span><span class="n">message</span><span class="p">)</span> <span class="p">/</span> <span class="mi">2</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">game</span><span class="p">.</span><span class="n">screenSize</span><span class="p">.</span><span class="n">height</span> <span class="p">/</span> <span class="mi">2</span> <span class="p">+</span> <span class="mi">50</span> </span></span><span class="line"><span class="cl"> <span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>The first line sets the color to <code>Color.black</code> which means whatever we&rsquo;re going to draw next, it will have the black color. It does not apply to the images of course, but it will affect the color of the geometrical primitives as well as fonts.</p> <p>The second line just fills the screen with the previously specified color.</p> <p>The next two blocks are more involved, but they just repeat the same pattern twice:</p> <ol> <li>Set font</li> <li>Set color</li> <li>Draw text</li> </ol> <p>We use different fonts for the game title and for the hint message, but we just repeat the same steps, the only difference is in the values.</p> <h3 id="testing">Testing</h3> <p>Let&rsquo;s check this scene to see if it works. Now we can assign it to our <code>Game</code> instance in the <code>Main.kt</code>:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">package</span> <span class="nn">com.bubelov.snake</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">import</span> <span class="nn">com.bubelov.snake.engine.GameFactory</span> </span></span><span class="line"><span class="cl"><span class="k">import</span> <span class="nn">com.bubelov.snake.scene.MainMenuScene</span> </span></span><span class="line"><span class="cl"><span class="k">import</span> <span class="nn">java.awt.Dimension</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">fun</span> <span class="nf">main</span><span class="p">(</span><span class="n">args</span><span class="p">:</span> <span class="n">Array</span><span class="p">&lt;</span><span class="n">String</span><span class="p">&gt;)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">screenSize</span> <span class="p">=</span> <span class="n">Dimension</span><span class="p">(</span><span class="mi">660</span><span class="p">,</span> <span class="mi">660</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">game</span> <span class="p">=</span> <span class="nc">GameFactory</span><span class="p">.</span><span class="n">create</span><span class="p">(</span><span class="n">screenSize</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">game</span><span class="p">.</span><span class="n">scene</span> <span class="p">=</span> <span class="n">MainMenuScene</span><span class="p">(</span><span class="n">game</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">game</span><span class="p">.</span><span class="n">play</span><span class="p">()</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>Now let&rsquo;s launch our game. You should see the following screen:</p> <p><figure> <a href="https://bubelov.com/blog/2020/snake-kotlin/main-menu_hu_59341bf116a5c434.webp"> <img src="https://bubelov.com/blog/2020/snake-kotlin/main-menu_hu_fe4d96ed4a67f431.webp" alt="" /> </a> </figure></p> <h3 id="conclusion-4">Conclusion</h3> <p>Now we know the basics of creating the scenes, and we also have the game menu. Next, we&rsquo;re going to define the game model.</p> <h2 id="model">Model</h2> <p>We need a model layer to define what objects will exist within our game. We can define any object but since we&rsquo;re working on the snake game, it makes sense to start with the essentials: snake and apple. Feel free to add more objects such as bonuses if you think it would make the game more interesting.</p> <h3 id="snake-model">Snake Model</h3> <p>How can we represent a snake? As you may already know the traditional snake looked like a chain of blocks that constantly moves in a specific direction. Each part of the snake has its own position and this position changes when the snake moves.</p> <p>Let&rsquo;s create a new class and call it <code>SnakeBodyPart</code>:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">data</span> <span class="k">class</span> <span class="nc">SnakeBodyPart</span> <span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="k">var</span> <span class="py">x</span><span class="p">:</span> <span class="n">Int</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="k">var</span> <span class="py">y</span><span class="p">:</span> <span class="n">Int</span> </span></span><span class="line"><span class="cl"><span class="p">)</span> </span></span></code></pre></div><p>This class represents one piece of the snake. Usually the snake is quite short when the game starts, but it grows longer as it eats apples. Each apple consumed by our snake adds one more part to it&rsquo;s body. The only thing we want to know about each body part is it&rsquo;s position.</p> <p>Do we need something else except the list of body parts to describe the snake? It turns out the snake is a bit smarter than the sum of it&rsquo;s parts. At least, it should have a direction, and it should be able to move according to the selected direction.</p> <p>Let&rsquo;s create a <code>Snake</code> class that will coordinate the movement of all the body parts:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Snake</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">startX</span><span class="p">:</span> <span class="n">Int</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">startY</span><span class="p">:</span> <span class="n">Int</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="k">var</span> <span class="py">direction</span><span class="p">:</span> <span class="n">Direction</span> <span class="p">=</span> <span class="nc">Direction</span><span class="p">.</span><span class="n">RIGHT</span> </span></span><span class="line"><span class="cl"><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">body</span> <span class="p">=</span> <span class="n">mutableListOf</span><span class="p">&lt;</span><span class="n">SnakeBodyPart</span><span class="p">&gt;()</span> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">head</span> <span class="k">by</span> <span class="n">lazy</span> <span class="p">{</span> <span class="n">body</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">init</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">body</span> <span class="o">+=</span> <span class="n">SnakeBodyPart</span><span class="p">(</span><span class="n">startX</span><span class="p">,</span> <span class="n">startY</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">body</span> <span class="o">+=</span> <span class="n">SnakeBodyPart</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">x</span> <span class="p">=</span> <span class="n">startX</span> <span class="p">-</span> <span class="n">direction</span><span class="p">.</span><span class="n">deltaX</span><span class="p">(),</span> </span></span><span class="line"><span class="cl"> <span class="n">y</span> <span class="p">=</span> <span class="n">startY</span> <span class="p">-</span> <span class="n">direction</span><span class="p">.</span><span class="n">deltaY</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> <span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">body</span> <span class="o">+=</span> <span class="n">SnakeBodyPart</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">x</span> <span class="p">=</span> <span class="n">startX</span> <span class="p">-</span> <span class="n">direction</span><span class="p">.</span><span class="n">deltaX</span><span class="p">()</span> <span class="p">*</span> <span class="mi">2</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">y</span> <span class="p">=</span> <span class="n">startY</span> <span class="p">-</span> <span class="n">direction</span><span class="p">.</span><span class="n">deltaY</span><span class="p">()</span> <span class="p">*</span> <span class="mi">2</span> </span></span><span class="line"><span class="cl"> <span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">fun</span> <span class="nf">move</span><span class="p">()</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="k">in</span> <span class="n">body</span><span class="p">.</span><span class="n">size</span> <span class="p">-</span> <span class="mi">1</span> <span class="n">downTo</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">current</span> <span class="p">=</span> <span class="n">body</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="err">(</span><span class="py">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span> <span class="p">=</span> <span class="n">body</span><span class="p">[</span><span class="n">i</span> <span class="p">-</span> <span class="mi">1</span><span class="p">]</span> </span></span><span class="line"><span class="cl"> <span class="n">current</span><span class="p">.</span><span class="n">x</span> <span class="p">=</span> <span class="n">x</span> </span></span><span class="line"><span class="cl"> <span class="n">current</span><span class="p">.</span><span class="n">y</span> <span class="p">=</span> <span class="n">y</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">head</span><span class="p">.</span><span class="n">x</span> <span class="p">=</span> <span class="n">head</span><span class="p">.</span><span class="n">x</span> <span class="p">+</span> <span class="n">direction</span><span class="p">.</span><span class="n">deltaX</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> <span class="n">head</span><span class="p">.</span><span class="n">y</span> <span class="p">=</span> <span class="n">head</span><span class="p">.</span><span class="n">y</span> <span class="p">+</span> <span class="n">direction</span><span class="p">.</span><span class="n">deltaY</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>This class handles the creation of the snake at the specific location as well as moving it in any specific direction. The only missing part is the <code>Direction</code> enum:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">enum</span> <span class="k">class</span> <span class="nc">Direction</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">UP</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">RIGHT</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">DOWN</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">LEFT</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">fun</span> <span class="nf">deltaX</span><span class="p">():</span> <span class="n">Int</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="k">when</span> <span class="p">(</span><span class="k">this</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">LEFT</span> <span class="o">-&gt;</span> <span class="p">-</span><span class="mi">1</span> </span></span><span class="line"><span class="cl"> <span class="n">RIGHT</span> <span class="o">-&gt;</span> <span class="mi">1</span> </span></span><span class="line"><span class="cl"> <span class="k">else</span> <span class="o">-&gt;</span> <span class="mi">0</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">fun</span> <span class="nf">deltaY</span><span class="p">():</span> <span class="n">Int</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="k">when</span> <span class="p">(</span><span class="k">this</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">UP</span> <span class="o">-&gt;</span> <span class="mi">1</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="n">DOWN</span> <span class="o">-&gt;</span> <span class="p">-</span><span class="mi">1</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="k">else</span> <span class="o">-&gt;</span> <span class="mi">0</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>Note that we have 2 helper methods to calculate how a particular direction affects x and y coordinates of the snake. For instance, the UP direction will produce <code>deltaY = -1</code> and <code>deltaX = 0</code>.</p> <h3 id="apple-model">Apple Model</h3> <p>The apple model is very simple. The only thing we need to know is the position of an apple which can be described as a pair of integers (x and y):</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">data</span> <span class="k">class</span> <span class="nc">Apple</span> <span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">x</span><span class="p">:</span> <span class="n">Int</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">y</span><span class="p">:</span> <span class="n">Int</span> </span></span><span class="line"><span class="cl"><span class="p">)</span> </span></span></code></pre></div><h3 id="conclusion-5">Conclusion</h3> <p>Now we have a model which describes all objects that can exist in our game&rsquo;s world. Next, we will place those objects on the game screen and make them interact with the player and with each other.</p> <h2 id="game-scene">Game Scene</h2> <p>Now that we have all the required components we can start working on the actual game mechanics. In this post we&rsquo;re going to create the <code>GameScene</code> class, the main scene where most of the action happens.</p> <h3 id="movement">Movement</h3> <p>Let&rsquo;s think about snake movement. When should it move? We need some real numbers. The easiest way to move the snake is to call it&rsquo;s <code>move</code> method on every scene update. It might seem like a good idea but let&rsquo;s think about how many times that method is supposed to be called? The answer is: we don&rsquo;t know, and we can&rsquo;t know, most likely it will be called too often. Not only does it depend on a particular machine, it also can be called hundreds of times per second which will move the snake too fast making our game unplayable.</p> <p>Our game world is just a grid of squares, so a square seems to be a great distance unit. How can we describe the speed? Distance units per second sounds reasonable so let&rsquo;s specify our requirements:</p> <p>The snake needs to move at a fixed pace of 1 square per 300 milliseconds, roughly 3 squares per second. The speed can be increased as the game progresses, feel free to play with this parameter.</p> <p>How can we achieve such a behavior? I suggest we make an assumption that the scene will be updated faster than 3 times per second, so we need to move the snake on some updates but keep it still during other invocations of that same method.</p> <p>Basically, we need a timer. Let&rsquo;s add a new class and name it <code>Timer</code>:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Timer</span><span class="p">(</span><span class="k">private</span> <span class="k">val</span> <span class="py">duration</span><span class="p">:</span> <span class="n">Long</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">private</span> <span class="k">var</span> <span class="py">remainingTime</span> <span class="p">=</span> <span class="n">duration</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">fun</span> <span class="nf">update</span><span class="p">(</span><span class="n">timePassed</span><span class="p">:</span> <span class="n">Long</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">remainingTime</span> <span class="o">-=</span> <span class="n">timePassed</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">fun</span> <span class="nf">timeIsUp</span><span class="p">()</span> <span class="p">=</span> <span class="n">remainingTime</span> <span class="o">&lt;=</span> <span class="mi">0</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">fun</span> <span class="nf">reset</span><span class="p">()</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">remainingTime</span> <span class="p">=</span> <span class="n">duration</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>This class also has an update method, just like our scene, but it only holds the remaining time until it should be fired so we can keep updating it, but it will not be fired until the time comes. In case the time is up we should perform our delayed event and reset the timer (if we want it to be repeating).</p> <h3 id="game-scene-1">Game Scene</h3> <p>It looks like now we have everything to implement the game scene, let&rsquo;s add a new class and call it <code>GameScene</code>:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">import</span> <span class="nn">java.awt.Color</span> </span></span><span class="line"><span class="cl"><span class="k">import</span> <span class="nn">java.awt.Graphics2D</span> </span></span><span class="line"><span class="cl"><span class="k">import</span> <span class="nn">java.awt.event.KeyEvent</span> </span></span><span class="line"><span class="cl"><span class="k">import</span> <span class="nn">java.util.concurrent.TimeUnit</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">GameScene</span><span class="p">(</span><span class="n">game</span><span class="p">:</span> <span class="n">Game</span><span class="p">)</span> <span class="p">:</span> <span class="n">Scene</span><span class="p">(</span><span class="n">game</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">private</span> <span class="k">val</span> <span class="py">snake</span> <span class="p">=</span> <span class="n">Snake</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">startX</span> <span class="p">=</span> <span class="n">WORLD_WIDTH</span> <span class="p">/</span> <span class="mi">2</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">startY</span> <span class="p">=</span> <span class="n">WORLD_HEIGHT</span> <span class="p">/</span> <span class="mi">2</span> </span></span><span class="line"><span class="cl"> <span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">private</span> <span class="k">lateinit</span> <span class="k">var</span> <span class="py">apple</span><span class="p">:</span> <span class="n">Apple</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">private</span> <span class="k">val</span> <span class="py">snakeMoveTimer</span> <span class="p">=</span> <span class="n">Timer</span><span class="p">(</span><span class="nc">TimeUnit</span><span class="p">.</span><span class="nc">MILLISECONDS</span><span class="p">.</span><span class="n">toNanos</span><span class="p">(</span><span class="mi">300</span><span class="p">))</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">init</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">placeApple</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">override</span> <span class="k">fun</span> <span class="nf">update</span><span class="p">(</span><span class="n">timePassed</span><span class="p">:</span> <span class="n">Long</span><span class="p">,</span> <span class="n">timeUnits</span><span class="p">:</span> <span class="n">TimeUnit</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="n">gameIsOver</span><span class="p">())</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="c1">// game.scene = GameOverScene(game) TODO Implement later </span></span></span><span class="line"><span class="cl"> <span class="k">return</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">processInput</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">snakeMoveTimer</span><span class="p">.</span><span class="n">update</span><span class="p">(</span><span class="n">timePassed</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="n">snakeMoveTimer</span><span class="p">.</span><span class="n">timeIsUp</span><span class="p">())</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">snake</span><span class="p">.</span><span class="n">move</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">head</span> <span class="p">=</span> <span class="n">snake</span><span class="p">.</span><span class="n">head</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="n">head</span><span class="p">.</span><span class="n">x</span> <span class="p">&lt;</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">head</span><span class="p">.</span><span class="n">x</span> <span class="p">=</span> <span class="n">WORLD_WIDTH</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="n">head</span><span class="p">.</span><span class="n">x</span> <span class="p">&gt;</span> <span class="n">WORLD_WIDTH</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">head</span><span class="p">.</span><span class="n">x</span> <span class="p">=</span> <span class="mi">1</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="n">head</span><span class="p">.</span><span class="n">y</span> <span class="p">&lt;</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">head</span><span class="p">.</span><span class="n">y</span> <span class="p">=</span> <span class="n">WORLD_HEIGHT</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="n">head</span><span class="p">.</span><span class="n">y</span> <span class="p">&gt;</span> <span class="n">WORLD_HEIGHT</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">head</span><span class="p">.</span><span class="n">y</span> <span class="p">=</span> <span class="mi">1</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="n">head</span><span class="p">.</span><span class="n">x</span> <span class="o">==</span> <span class="n">apple</span><span class="p">.</span><span class="n">x</span> <span class="o">&amp;&amp;</span> <span class="n">head</span><span class="p">.</span><span class="n">y</span> <span class="o">==</span> <span class="n">apple</span><span class="p">.</span><span class="n">y</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">body</span> <span class="p">=</span> <span class="n">snake</span><span class="p">.</span><span class="n">body</span> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">lastPart</span> <span class="p">=</span> <span class="n">body</span><span class="p">[</span><span class="n">body</span><span class="p">.</span><span class="n">size</span> <span class="p">-</span> <span class="mi">1</span><span class="p">]</span> </span></span><span class="line"><span class="cl"> <span class="n">body</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">SnakeBodyPart</span><span class="p">(</span><span class="n">lastPart</span><span class="p">.</span><span class="n">x</span><span class="p">,</span> <span class="n">lastPart</span><span class="p">.</span><span class="n">y</span><span class="p">))</span> </span></span><span class="line"><span class="cl"> <span class="n">placeApple</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">snakeMoveTimer</span><span class="p">.</span><span class="n">reset</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">override</span> <span class="k">fun</span> <span class="nf">draw</span><span class="p">(</span><span class="n">graphics</span><span class="p">:</span> <span class="n">Graphics2D</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">graphics</span><span class="p">.</span><span class="n">apply</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">color</span> <span class="p">=</span> <span class="nc">Color</span><span class="p">.</span><span class="n">black</span> </span></span><span class="line"><span class="cl"> <span class="n">fillRect</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">game</span><span class="p">.</span><span class="n">width</span><span class="p">,</span> <span class="n">game</span><span class="p">.</span><span class="n">height</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">drawSnake</span><span class="p">(</span><span class="k">this</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">drawApple</span><span class="p">(</span><span class="k">this</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">private</span> <span class="k">fun</span> <span class="nf">processInput</span><span class="p">()</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="p">(</span><span class="n">event</span> <span class="k">in</span> <span class="n">game</span><span class="p">.</span><span class="n">input</span><span class="p">.</span><span class="n">consumeEvents</span><span class="p">())</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">when</span> <span class="p">(</span><span class="n">event</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">is</span> <span class="nc">Input</span><span class="p">.</span><span class="nc">Event</span><span class="p">.</span><span class="n">KeyPressed</span> <span class="o">-&gt;</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">when</span> <span class="p">(</span><span class="n">event</span><span class="p">.</span><span class="k">data</span><span class="p">.</span><span class="n">keyCode</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nc">KeyEvent</span><span class="p">.</span><span class="n">VK_UP</span> <span class="o">-&gt;</span> <span class="n">snake</span><span class="p">.</span><span class="n">direction</span> <span class="p">=</span> <span class="nc">Direction</span><span class="p">.</span><span class="n">UP</span> </span></span><span class="line"><span class="cl"> <span class="nc">KeyEvent</span><span class="p">.</span><span class="n">VK_RIGHT</span> <span class="o">-&gt;</span> <span class="n">snake</span><span class="p">.</span><span class="n">direction</span> <span class="p">=</span> <span class="nc">Direction</span><span class="p">.</span><span class="n">RIGHT</span> </span></span><span class="line"><span class="cl"> <span class="nc">KeyEvent</span><span class="p">.</span><span class="n">VK_DOWN</span> <span class="o">-&gt;</span> <span class="n">snake</span><span class="p">.</span><span class="n">direction</span> <span class="p">=</span> <span class="nc">Direction</span><span class="p">.</span><span class="n">DOWN</span> </span></span><span class="line"><span class="cl"> <span class="nc">KeyEvent</span><span class="p">.</span><span class="n">VK_LEFT</span> <span class="o">-&gt;</span> <span class="n">snake</span><span class="p">.</span><span class="n">direction</span> <span class="p">=</span> <span class="nc">Direction</span><span class="p">.</span><span class="n">LEFT</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">private</span> <span class="k">fun</span> <span class="nf">drawSnake</span><span class="p">(</span><span class="n">graphics</span><span class="p">:</span> <span class="n">Graphics2D</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">graphics</span><span class="p">.</span><span class="n">apply</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">color</span> <span class="p">=</span> <span class="nc">Color</span><span class="p">.</span><span class="n">green</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">snake</span><span class="p">.</span><span class="n">body</span><span class="p">.</span><span class="n">forEach</span> <span class="p">{</span> <span class="n">part</span> <span class="o">-&gt;</span> </span></span><span class="line"><span class="cl"> <span class="n">fillRect</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">part</span><span class="p">.</span><span class="n">x</span> <span class="p">*</span> <span class="n">CELL_SIZE</span> <span class="p">-</span> <span class="n">CELL_SIZE</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">game</span><span class="p">.</span><span class="n">screenSize</span><span class="p">.</span><span class="n">height</span> <span class="p">-</span> <span class="n">part</span><span class="p">.</span><span class="n">y</span> <span class="p">*</span> <span class="n">CELL_SIZE</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">CELL_SIZE</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">CELL_SIZE</span> </span></span><span class="line"><span class="cl"> <span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">private</span> <span class="k">fun</span> <span class="nf">drawApple</span><span class="p">(</span><span class="n">graphics</span><span class="p">:</span> <span class="n">Graphics2D</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">graphics</span><span class="p">.</span><span class="n">apply</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">color</span> <span class="p">=</span> <span class="nc">Color</span><span class="p">.</span><span class="n">red</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">fillRect</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">apple</span><span class="p">.</span><span class="n">x</span> <span class="p">*</span> <span class="n">CELL_SIZE</span> <span class="p">-</span> <span class="n">CELL_SIZE</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">game</span><span class="p">.</span><span class="n">screenSize</span><span class="p">.</span><span class="n">height</span> <span class="p">-</span> <span class="n">apple</span><span class="p">.</span><span class="n">y</span> <span class="p">*</span> <span class="n">CELL_SIZE</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">CELL_SIZE</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">CELL_SIZE</span> </span></span><span class="line"><span class="cl"> <span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">private</span> <span class="k">fun</span> <span class="nf">placeApple</span><span class="p">()</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">var</span> <span class="py">x</span> <span class="p">=</span> <span class="p">(</span><span class="mi">1</span> <span class="p">+</span> <span class="p">(</span><span class="nc">Math</span><span class="p">.</span><span class="n">random</span><span class="p">()</span> <span class="p">*</span> <span class="n">WORLD_WIDTH</span><span class="p">)).</span><span class="n">toInt</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> <span class="k">var</span> <span class="py">y</span> <span class="p">=</span> <span class="p">(</span><span class="mi">1</span> <span class="p">+</span> <span class="p">(</span><span class="nc">Math</span><span class="p">.</span><span class="n">random</span><span class="p">()</span> <span class="p">*</span> <span class="n">WORLD_HEIGHT</span><span class="p">)).</span><span class="n">toInt</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">while</span> <span class="p">(</span><span class="o">!is</span><span class="n">CellEmpty</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">))</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="n">x</span> <span class="p">&lt;</span> <span class="n">WORLD_WIDTH</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">x</span><span class="o">++</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="n">y</span> <span class="p">&lt;</span> <span class="n">WORLD_HEIGHT</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">x</span> <span class="p">=</span> <span class="mi">1</span> </span></span><span class="line"><span class="cl"> <span class="n">y</span><span class="o">++</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">x</span> <span class="p">=</span> <span class="mi">1</span> </span></span><span class="line"><span class="cl"> <span class="n">y</span> <span class="p">=</span> <span class="mi">1</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">apple</span> <span class="p">=</span> <span class="n">Apple</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">private</span> <span class="k">fun</span> <span class="nf">isCellEmpty</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">Int</span><span class="p">,</span> <span class="n">y</span><span class="p">:</span> <span class="n">Int</span><span class="p">)</span> <span class="p">=</span> <span class="n">snake</span><span class="p">.</span><span class="n">body</span><span class="p">.</span><span class="n">none</span> <span class="p">{</span> <span class="k">it</span><span class="p">.</span><span class="n">x</span> <span class="o">==</span> <span class="n">x</span> <span class="o">&amp;&amp;</span> <span class="k">it</span><span class="p">.</span><span class="n">y</span> <span class="o">==</span> <span class="n">y</span> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">private</span> <span class="k">fun</span> <span class="nf">gameIsOver</span><span class="p">():</span> <span class="n">Boolean</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="n">snake</span><span class="p">.</span><span class="n">body</span><span class="p">.</span><span class="n">size</span> <span class="o">==</span> <span class="n">WORLD_WIDTH</span> <span class="p">*</span> <span class="n">WORLD_HEIGHT</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="k">true</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">snake</span><span class="p">.</span><span class="n">body</span><span class="p">.</span><span class="n">forEachIndexed</span> <span class="p">{</span> <span class="n">index</span><span class="p">,</span> <span class="n">part</span> <span class="o">-&gt;</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="n">index</span> <span class="p">&gt;</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">part</span><span class="p">.</span><span class="n">x</span> <span class="o">==</span> <span class="n">snake</span><span class="p">.</span><span class="n">head</span><span class="p">.</span><span class="n">x</span> <span class="o">&amp;&amp;</span> <span class="n">part</span><span class="p">.</span><span class="n">y</span> <span class="o">==</span> <span class="n">snake</span><span class="p">.</span><span class="n">head</span><span class="p">.</span><span class="n">y</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="k">true</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="k">false</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">companion</span> <span class="k">object</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">const</span> <span class="k">val</span> <span class="py">WORLD</span><span class="n">_WIDTH</span> <span class="p">=</span> <span class="mi">12</span> </span></span><span class="line"><span class="cl"> <span class="k">const</span> <span class="k">val</span> <span class="py">WORLD</span><span class="n">_HEIGHT</span> <span class="p">=</span> <span class="mi">12</span> </span></span><span class="line"><span class="cl"> <span class="k">const</span> <span class="k">val</span> <span class="py">CELL</span><span class="n">_SIZE</span> <span class="p">=</span> <span class="mi">55</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>Let&rsquo;s go through this code line by line to better understand what&rsquo;s happening:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">private</span> <span class="k">val</span> <span class="py">snake</span> <span class="p">=</span> <span class="n">Snake</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">startX</span> <span class="p">=</span> <span class="n">WORLD_WIDTH</span> <span class="p">/</span> <span class="mi">2</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">startY</span> <span class="p">=</span> <span class="n">WORLD_HEIGHT</span> <span class="p">/</span> <span class="mi">2</span> </span></span><span class="line"><span class="cl"><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">private</span> <span class="k">lateinit</span> <span class="k">var</span> <span class="py">apple</span><span class="p">:</span> <span class="n">Apple</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">private</span> <span class="k">val</span> <span class="py">snakeMoveTimer</span> <span class="p">=</span> <span class="n">Timer</span><span class="p">(</span><span class="nc">TimeUnit</span><span class="p">.</span><span class="nc">MILLISECONDS</span><span class="p">.</span><span class="n">toNanos</span><span class="p">(</span><span class="mi">300</span><span class="p">))</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">init</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">placeApple</span><span class="p">()</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>Here we are just instantiating our snake and apple and setting up snake move timer to make sure it won&rsquo;t run too fast.</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">override</span> <span class="k">fun</span> <span class="nf">update</span><span class="p">(</span><span class="n">timePassed</span><span class="p">:</span> <span class="n">Long</span><span class="p">,</span> <span class="n">timeUnits</span><span class="p">:</span> <span class="n">TimeUnit</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="n">gameIsOver</span><span class="p">())</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="c1">// game.scene = GameOverScene(game) TODO Implement later </span></span></span><span class="line"><span class="cl"> <span class="k">return</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">processInput</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">snakeMoveTimer</span><span class="p">.</span><span class="n">update</span><span class="p">(</span><span class="n">timePassed</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="n">snakeMoveTimer</span><span class="p">.</span><span class="n">timeIsUp</span><span class="p">())</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">snake</span><span class="p">.</span><span class="n">move</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">head</span> <span class="p">=</span> <span class="n">snake</span><span class="p">.</span><span class="n">head</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="n">head</span><span class="p">.</span><span class="n">x</span> <span class="p">&lt;</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">head</span><span class="p">.</span><span class="n">x</span> <span class="p">=</span> <span class="n">WORLD_WIDTH</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="n">head</span><span class="p">.</span><span class="n">x</span> <span class="p">&gt;</span> <span class="n">WORLD_WIDTH</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">head</span><span class="p">.</span><span class="n">x</span> <span class="p">=</span> <span class="mi">1</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="n">head</span><span class="p">.</span><span class="n">y</span> <span class="p">&lt;</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">head</span><span class="p">.</span><span class="n">y</span> <span class="p">=</span> <span class="n">WORLD_HEIGHT</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="n">head</span><span class="p">.</span><span class="n">y</span> <span class="p">&gt;</span> <span class="n">WORLD_HEIGHT</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">head</span><span class="p">.</span><span class="n">y</span> <span class="p">=</span> <span class="mi">1</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="n">head</span><span class="p">.</span><span class="n">x</span> <span class="o">==</span> <span class="n">apple</span><span class="p">.</span><span class="n">x</span> <span class="o">&amp;&amp;</span> <span class="n">head</span><span class="p">.</span><span class="n">y</span> <span class="o">==</span> <span class="n">apple</span><span class="p">.</span><span class="n">y</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">body</span> <span class="p">=</span> <span class="n">snake</span><span class="p">.</span><span class="n">body</span> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">lastPart</span> <span class="p">=</span> <span class="n">body</span><span class="p">[</span><span class="n">body</span><span class="p">.</span><span class="n">size</span> <span class="p">-</span> <span class="mi">1</span><span class="p">]</span> </span></span><span class="line"><span class="cl"> <span class="n">body</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">SnakeBodyPart</span><span class="p">(</span><span class="n">lastPart</span><span class="p">.</span><span class="n">x</span><span class="p">,</span> <span class="n">lastPart</span><span class="p">.</span><span class="n">y</span><span class="p">))</span> </span></span><span class="line"><span class="cl"> <span class="n">placeApple</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">snakeMoveTimer</span><span class="p">.</span><span class="n">reset</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>This part is more interesting. The first thing we need to do on each update is to make sure the game is still playable. If the game is over (snake ate itself), we need to transition to the <code>GameOver</code> scene which will be implemented in the next post.</p> <p>The next step is to process the user input. After that, we need to update the snake move timer and check if it&rsquo;s time to move the snake. In case we need to move the snake, we should also check that it stays inside the screen bounds. That&rsquo;s why we need those 4 head position checks.</p> <p>We should also check if the snake has reached an apple. In that case we need to place another apple somewhere on the game screen and we should also elongate the snake length.</p> <p>Let&rsquo;s move to the next method:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">override</span> <span class="k">fun</span> <span class="nf">draw</span><span class="p">(</span><span class="n">graphics</span><span class="p">:</span> <span class="n">Graphics2D</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">graphics</span><span class="p">.</span><span class="n">apply</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">color</span> <span class="p">=</span> <span class="nc">Color</span><span class="p">.</span><span class="n">black</span> </span></span><span class="line"><span class="cl"> <span class="n">fillRect</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">game</span><span class="p">.</span><span class="n">width</span><span class="p">,</span> <span class="n">game</span><span class="p">.</span><span class="n">height</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">drawSnake</span><span class="p">(</span><span class="k">this</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">drawApple</span><span class="p">(</span><span class="k">this</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>Nothing interesting here, we&rsquo;re just filling the screen with black color and then drawing the snake and apple. Let&rsquo;s move forward:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">private</span> <span class="k">fun</span> <span class="nf">processInput</span><span class="p">()</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="p">(</span><span class="n">event</span> <span class="k">in</span> <span class="n">game</span><span class="p">.</span><span class="n">input</span><span class="p">.</span><span class="n">consumeEvents</span><span class="p">())</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">when</span> <span class="p">(</span><span class="n">event</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">is</span> <span class="nc">Input</span><span class="p">.</span><span class="nc">Event</span><span class="p">.</span><span class="n">KeyPressed</span> <span class="o">-&gt;</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">when</span> <span class="p">(</span><span class="n">event</span><span class="p">.</span><span class="k">data</span><span class="p">.</span><span class="n">keyCode</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nc">KeyEvent</span><span class="p">.</span><span class="n">VK_UP</span> <span class="o">-&gt;</span> <span class="n">snake</span><span class="p">.</span><span class="n">direction</span> <span class="p">=</span> <span class="nc">Direction</span><span class="p">.</span><span class="n">UP</span> </span></span><span class="line"><span class="cl"> <span class="nc">KeyEvent</span><span class="p">.</span><span class="n">VK_RIGHT</span> <span class="o">-&gt;</span> <span class="n">snake</span><span class="p">.</span><span class="n">direction</span> <span class="p">=</span> <span class="nc">Direction</span><span class="p">.</span><span class="n">RIGHT</span> </span></span><span class="line"><span class="cl"> <span class="nc">KeyEvent</span><span class="p">.</span><span class="n">VK_DOWN</span> <span class="o">-&gt;</span> <span class="n">snake</span><span class="p">.</span><span class="n">direction</span> <span class="p">=</span> <span class="nc">Direction</span><span class="p">.</span><span class="n">DOWN</span> </span></span><span class="line"><span class="cl"> <span class="nc">KeyEvent</span><span class="p">.</span><span class="n">VK_LEFT</span> <span class="o">-&gt;</span> <span class="n">snake</span><span class="p">.</span><span class="n">direction</span> <span class="p">=</span> <span class="nc">Direction</span><span class="p">.</span><span class="n">LEFT</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>Here we are analyzing the pressed keys and setting the snake direction according to user input.</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">private</span> <span class="k">fun</span> <span class="nf">drawSnake</span><span class="p">(</span><span class="n">graphics</span><span class="p">:</span> <span class="n">Graphics2D</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">graphics</span><span class="p">.</span><span class="n">apply</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">color</span> <span class="p">=</span> <span class="nc">Color</span><span class="p">.</span><span class="n">red</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">snake</span><span class="p">.</span><span class="n">body</span><span class="p">.</span><span class="n">forEach</span> <span class="p">{</span> <span class="n">part</span> <span class="o">-&gt;</span> </span></span><span class="line"><span class="cl"> <span class="n">fillRect</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">part</span><span class="p">.</span><span class="n">x</span> <span class="p">*</span> <span class="n">CELL_SIZE</span> <span class="p">-</span> <span class="n">CELL_SIZE</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">game</span><span class="p">.</span><span class="n">screenSize</span><span class="p">.</span><span class="n">height</span> <span class="p">-</span> <span class="n">part</span><span class="p">.</span><span class="n">y</span> <span class="p">*</span> <span class="n">CELL_SIZE</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">CELL_SIZE</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">CELL_SIZE</span> </span></span><span class="line"><span class="cl"> <span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">private</span> <span class="k">fun</span> <span class="nf">drawApple</span><span class="p">(</span><span class="n">graphics</span><span class="p">:</span> <span class="n">Graphics2D</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">graphics</span><span class="p">.</span><span class="n">apply</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">color</span> <span class="p">=</span> <span class="nc">Color</span><span class="p">.</span><span class="n">green</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">fillRect</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">apple</span><span class="p">.</span><span class="n">x</span> <span class="p">*</span> <span class="n">CELL_SIZE</span> <span class="p">-</span> <span class="n">CELL_SIZE</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">game</span><span class="p">.</span><span class="n">screenSize</span><span class="p">.</span><span class="n">height</span> <span class="p">-</span> <span class="n">apple</span><span class="p">.</span><span class="n">y</span> <span class="p">*</span> <span class="n">CELL_SIZE</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">CELL_SIZE</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">CELL_SIZE</span> </span></span><span class="line"><span class="cl"> <span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>Those methods are used to draw the game objects. As you can see we can just fill the rectangles with different colors to make a distinction between snake body and apples: snake body is green but apples are red.</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">private</span> <span class="k">fun</span> <span class="nf">placeApple</span><span class="p">()</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">var</span> <span class="py">x</span> <span class="p">=</span> <span class="p">(</span><span class="mi">1</span> <span class="p">+</span> <span class="p">(</span><span class="nc">Math</span><span class="p">.</span><span class="n">random</span><span class="p">()</span> <span class="p">*</span> <span class="n">WORLD_WIDTH</span><span class="p">)).</span><span class="n">toInt</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> <span class="k">var</span> <span class="py">y</span> <span class="p">=</span> <span class="p">(</span><span class="mi">1</span> <span class="p">+</span> <span class="p">(</span><span class="nc">Math</span><span class="p">.</span><span class="n">random</span><span class="p">()</span> <span class="p">*</span> <span class="n">WORLD_HEIGHT</span><span class="p">)).</span><span class="n">toInt</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">while</span> <span class="p">(</span><span class="o">!is</span><span class="n">CellEmpty</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">))</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="n">x</span> <span class="p">&lt;</span> <span class="n">WORLD_WIDTH</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">x</span><span class="o">++</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="n">y</span> <span class="p">&lt;</span> <span class="n">WORLD_HEIGHT</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">x</span> <span class="p">=</span> <span class="mi">1</span> </span></span><span class="line"><span class="cl"> <span class="n">y</span><span class="o">++</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">x</span> <span class="p">=</span> <span class="mi">1</span> </span></span><span class="line"><span class="cl"> <span class="n">y</span> <span class="p">=</span> <span class="mi">1</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">apple</span> <span class="p">=</span> <span class="n">Apple</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">private</span> <span class="k">fun</span> <span class="nf">isCellEmpty</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">Int</span><span class="p">,</span> <span class="n">y</span><span class="p">:</span> <span class="n">Int</span><span class="p">)</span> <span class="p">=</span> <span class="n">snake</span><span class="p">.</span><span class="n">body</span><span class="p">.</span><span class="n">none</span> <span class="p">{</span> <span class="k">it</span><span class="p">.</span><span class="n">x</span> <span class="o">==</span> <span class="n">x</span> <span class="o">&amp;&amp;</span> <span class="k">it</span><span class="p">.</span><span class="n">y</span> <span class="o">==</span> <span class="n">y</span> <span class="p">}</span> </span></span></code></pre></div><p>This block can be a bit harder to understand. Basically, we need to find an empty cell to place a new apple here but the location cannot be predictable, so we should start with a random point. If that point is empty - that&rsquo;s it, but if it&rsquo;s not we need to scan our game grid line by line in order to find an empty cell. In that case we should use the first empty cell we find while scanning.</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">private</span> <span class="k">fun</span> <span class="nf">gameIsOver</span><span class="p">():</span> <span class="n">Boolean</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="n">snake</span><span class="p">.</span><span class="n">body</span><span class="p">.</span><span class="n">size</span> <span class="o">==</span> <span class="n">WORLD_WIDTH</span> <span class="p">*</span> <span class="n">WORLD_HEIGHT</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="k">true</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">snake</span><span class="p">.</span><span class="n">body</span><span class="p">.</span><span class="n">forEachIndexed</span> <span class="p">{</span> <span class="n">index</span><span class="p">,</span> <span class="n">part</span> <span class="o">-&gt;</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="n">index</span> <span class="p">&gt;</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">part</span><span class="p">.</span><span class="n">x</span> <span class="o">==</span> <span class="n">snake</span><span class="p">.</span><span class="n">head</span><span class="p">.</span><span class="n">x</span> <span class="o">&amp;&amp;</span> <span class="n">part</span><span class="p">.</span><span class="n">y</span> <span class="o">==</span> <span class="n">snake</span><span class="p">.</span><span class="n">head</span><span class="p">.</span><span class="n">y</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="k">true</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="k">false</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>What is a game over state? There are 2 possible outcomes:</p> <ul> <li><strong>winning</strong> (snake took all the space)</li> <li><strong>losing</strong> (snake tried to eat itself)</li> </ul> <p>That&rsquo;s exactly what we are checking for.</p> <h3 id="running-the-game">Running The Game</h3> <p>Let&rsquo;s run our game and try to play it. You should see the following picture:</p> <p><figure> <a href="https://bubelov.com/blog/2020/snake-kotlin/game-scene_hu_55ed04328ecf0687.webp"> <img src="https://bubelov.com/blog/2020/snake-kotlin/game-scene_hu_6b89f1e158f7a904.webp" alt="" /> </a> </figure></p> <p>Don&rsquo;t forget to uncomment screen transition on the <code>MainMenuScene</code>.</p> <h3 id="conclusion-6">Conclusion</h3> <p>Our game is almost ready, we just need to add the &ldquo;game over&rdquo; scene and reflect on the things that we&rsquo;ve implemented so far.</p> <h2 id="finalizing-the-game">Finalizing The Game</h2> <h3 id="game-over-scene">Game Over Scene</h3> <p>Our game needs one more scene in order to be completed: <code>GameOverScene</code>, let&rsquo;s add it to our project:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">import</span> <span class="nn">java.awt.Color</span> </span></span><span class="line"><span class="cl"><span class="k">import</span> <span class="nn">java.awt.Font</span> </span></span><span class="line"><span class="cl"><span class="k">import</span> <span class="nn">java.awt.Font.BOLD</span> </span></span><span class="line"><span class="cl"><span class="k">import</span> <span class="nn">java.awt.Graphics2D</span> </span></span><span class="line"><span class="cl"><span class="k">import</span> <span class="nn">java.awt.event.KeyEvent</span> </span></span><span class="line"><span class="cl"><span class="k">import</span> <span class="nn">java.util.concurrent.TimeUnit</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">GameOverScene</span><span class="p">(</span><span class="n">game</span><span class="p">:</span> <span class="n">Game</span><span class="p">)</span> <span class="p">:</span> <span class="n">Scene</span><span class="p">(</span><span class="n">game</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">override</span> <span class="k">fun</span> <span class="nf">update</span><span class="p">(</span><span class="n">timePassed</span><span class="p">:</span> <span class="n">Long</span><span class="p">,</span> <span class="n">timeUnits</span><span class="p">:</span> <span class="n">TimeUnit</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">game</span><span class="p">.</span><span class="n">input</span><span class="p">.</span><span class="n">consumeEvents</span><span class="p">().</span><span class="n">forEach</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">when</span> <span class="p">(</span><span class="k">it</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">is</span> <span class="nc">Input</span><span class="p">.</span><span class="nc">Event</span><span class="p">.</span><span class="n">KeyPressed</span> <span class="o">-&gt;</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">when</span> <span class="p">(</span><span class="k">it</span><span class="p">.</span><span class="k">data</span><span class="p">.</span><span class="n">keyCode</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nc">KeyEvent</span><span class="p">.</span><span class="n">VK_ENTER</span> <span class="o">-&gt;</span> <span class="n">game</span><span class="p">.</span><span class="n">scene</span> <span class="p">=</span> <span class="n">GameScene</span><span class="p">(</span><span class="n">game</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">override</span> <span class="k">fun</span> <span class="nf">draw</span><span class="p">(</span><span class="n">graphics</span><span class="p">:</span> <span class="n">Graphics2D</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">graphics</span><span class="p">.</span><span class="n">apply</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">color</span> <span class="p">=</span> <span class="nc">Color</span><span class="p">.</span><span class="n">black</span> </span></span><span class="line"><span class="cl"> <span class="n">fillRect</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">game</span><span class="p">.</span><span class="n">screenSize</span><span class="p">.</span><span class="n">width</span><span class="p">,</span> <span class="n">game</span><span class="p">.</span><span class="n">screenSize</span><span class="p">.</span><span class="n">height</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">font</span> <span class="p">=</span> <span class="n">Font</span><span class="p">(</span><span class="s2">&#34;Default&#34;</span><span class="p">,</span> <span class="n">BOLD</span><span class="p">,</span> <span class="mi">16</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">color</span> <span class="p">=</span> <span class="nc">Color</span><span class="p">.</span><span class="n">white</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">message</span> <span class="p">=</span> <span class="s2">&#34;Press &lt;Enter&gt; to start new game&#34;</span> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">messageBounds</span> <span class="p">=</span> <span class="n">fontMetrics</span><span class="p">.</span><span class="n">getStringBounds</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="k">this</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">messageWidth</span> <span class="p">=</span> <span class="n">messageBounds</span><span class="p">.</span><span class="n">width</span><span class="p">.</span><span class="n">toInt</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">messageHeight</span> <span class="p">=</span> <span class="n">messageBounds</span><span class="p">.</span><span class="n">height</span><span class="p">.</span><span class="n">toInt</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">drawString</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">message</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">game</span><span class="p">.</span><span class="n">screenSize</span><span class="p">.</span><span class="n">width</span> <span class="p">/</span> <span class="mi">2</span> <span class="p">-</span> <span class="n">messageWidth</span> <span class="p">/</span> <span class="mi">2</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">game</span><span class="p">.</span><span class="n">screenSize</span><span class="p">.</span><span class="n">height</span> <span class="p">/</span> <span class="mi">2</span> <span class="p">-</span> <span class="n">messageHeight</span> <span class="p">/</span> <span class="mi">2</span> </span></span><span class="line"><span class="cl"> <span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>There are 2 major steps happening here:</p> <ul> <li>Scanning the user input</li> <li>Drawing hint text in the center of our new scene</li> </ul> <p>Let&rsquo;s go through those steps one by one:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">override</span> <span class="k">fun</span> <span class="nf">update</span><span class="p">(</span><span class="n">nanosecondsPassed</span><span class="p">:</span> <span class="n">Long</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">game</span><span class="p">.</span><span class="n">input</span><span class="p">.</span><span class="n">consumeEvents</span><span class="p">().</span><span class="n">forEach</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">when</span> <span class="p">(</span><span class="k">it</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">is</span> <span class="nc">Input</span><span class="p">.</span><span class="nc">Event</span><span class="p">.</span><span class="n">KeyPressed</span> <span class="o">-&gt;</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">when</span> <span class="p">(</span><span class="k">it</span><span class="p">.</span><span class="k">data</span><span class="p">.</span><span class="n">keyCode</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nc">KeyEvent</span><span class="p">.</span><span class="n">VK_ENTER</span> <span class="o">-&gt;</span> <span class="n">game</span><span class="p">.</span><span class="n">scene</span> <span class="p">=</span> <span class="n">GameScene</span><span class="p">(</span><span class="n">game</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>This step is pretty straightforward, we need to scan through all of the input events in order to find out if the ENTER key was pressed. Pressing the ENTER key means we should navigate to the <code>GameScene</code> and restart our game.</p> <p>Let&rsquo;s move to the next step:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">override</span> <span class="k">fun</span> <span class="nf">draw</span><span class="p">(</span><span class="n">graphics</span><span class="p">:</span> <span class="n">Graphics2D</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">graphics</span><span class="p">.</span><span class="n">apply</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">color</span> <span class="p">=</span> <span class="nc">Color</span><span class="p">.</span><span class="n">black</span> </span></span><span class="line"><span class="cl"> <span class="n">fillRect</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">game</span><span class="p">.</span><span class="n">screenSize</span><span class="p">.</span><span class="n">width</span><span class="p">,</span> <span class="n">game</span><span class="p">.</span><span class="n">screenSize</span><span class="p">.</span><span class="n">height</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">font</span> <span class="p">=</span> <span class="n">Font</span><span class="p">(</span><span class="s2">&#34;Default&#34;</span><span class="p">,</span> <span class="n">BOLD</span><span class="p">,</span> <span class="mi">16</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">color</span> <span class="p">=</span> <span class="nc">Color</span><span class="p">.</span><span class="n">white</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">message</span> <span class="p">=</span> <span class="s2">&#34;Press &lt;Enter&gt; to start new game&#34;</span> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">messageBounds</span> <span class="p">=</span> <span class="n">fontMetrics</span><span class="p">.</span><span class="n">getStringBounds</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="k">this</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">messageWidth</span> <span class="p">=</span> <span class="n">messageBounds</span><span class="p">.</span><span class="n">width</span><span class="p">.</span><span class="n">toInt</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">messageHeight</span> <span class="p">=</span> <span class="n">messageBounds</span><span class="p">.</span><span class="n">height</span><span class="p">.</span><span class="n">toInt</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">drawString</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">message</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">game</span><span class="p">.</span><span class="n">screenSize</span><span class="p">.</span><span class="n">width</span> <span class="p">/</span> <span class="mi">2</span> <span class="p">-</span> <span class="n">messageWidth</span> <span class="p">/</span> <span class="mi">2</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">game</span><span class="p">.</span><span class="n">screenSize</span><span class="p">.</span><span class="n">height</span> <span class="p">/</span> <span class="mi">2</span> <span class="p">-</span> <span class="n">messageHeight</span> <span class="p">/</span> <span class="mi">2</span> </span></span><span class="line"><span class="cl"> <span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>The first 2 lines are responsible for filling the screen in black. The next two lines are just initializing the font that we want to use for drawing the text and the rest of the code is responsible for actually drawing it. Luckily for us, the Java SDK provides us with the <code>getStringBounds</code> method which can predict the size of the text that we&rsquo;re going to draw. Using those metrics, we can place our text at the center of the screen:</p> <figure><img src="https://bubelov.com/blog/2020/snake-kotlin/game-over-scene.png" alt="Game Over scene"> </figure> <h3 id="conclusion-7">Conclusion</h3> <p>In this series we&rsquo;ve covered the basics of game development in Kotlin, and we&rsquo;ve also created a fully playable snake game.</p> <p>There are far more in the game development than just that. Here is the steps that I recommend if you want to go further (the order is irrelevant):</p> <ul> <li> <p>Learn how to create graphics (raster, vector, 3D objects rendered to 2D images, doesn&rsquo;t matter).</p> </li> <li> <p>Learn how to create and add sound effects and music to your game.</p> </li> <li> <p>Learn a game engine, the best choice depends on your experience in programming. I&rsquo;d recommend Game Maker for total noobs, LibGDX for people who have solid programming skills and something like Unity if you want to sell your game or pursue a career in game development.</p> </li> <li> <p>Copy a few successful games with simple mechanics. It can teach you a lot about the art of making games.</p> </li> <li> <p>Don&rsquo;t be too hard on yourself. There are tons of things to learn, but it is worth it only if you enjoy the process!</p> </li> </ul> The Rust Programming Language https://bubelov.com/blog/2020/rust-programming-language/ Mon, 10 Feb 2020 00:00:00 +0000 https://bubelov.com/blog/2020/rust-programming-language/ <p>I like learning new stuff in daily chunks. It means, I usually have a few 40-minute breaks during my work day to learn something new about a selection of topics. One of the current topics is Rust and <a href="https://doc.rust-lang.org/book/">this book</a> works great with such a schedule. It helped me learn a few new things about Rust and the ways to write more idiomatic Rust code. This book might be helpful for anyone who wants to learn more about Rust.</p> Mobile Unleashed: The Origin and Evolution of ARM Processors in Our Devices https://bubelov.com/blog/2020/mobile-unleashed/ Sat, 08 Feb 2020 00:00:00 +0000 https://bubelov.com/blog/2020/mobile-unleashed/ <p><a href="https://www.amazon.com/Mobile-Unleashed-Evolution-Processors-Devices/dp/1519547269">This book</a> is mostly about the history of computing, although some information is still relevant. Reading such a book is a good way to understand the technical breakthroughs that underpin many consumer electronics products.</p> Investigating Cryptocurrencies: Understanding, Extracting, and Analyzing Blockchain Evidence https://bubelov.com/blog/2020/investigating-crypto/ Thu, 06 Feb 2020 00:00:00 +0000 https://bubelov.com/blog/2020/investigating-crypto/ <p>Although <a href="https://www.humblebundle.com/books/cybersecurity-2020-wiley-books">this book</a> is focused on how blockchains work, and it explains some things about them very well, that&rsquo;s not what caught my interest there. What I find most interesting about this book is the description of computer forensic experts&rsquo; workflow.</p> Industrial Society & Its Future https://bubelov.com/blog/2020/industrial-society-and-its-future/ Wed, 05 Feb 2020 00:00:00 +0000 https://bubelov.com/blog/2020/industrial-society-and-its-future/ <p>Ted Kaczynski, better known as Unibomber, had a pretty consistent worldview and this book of his have a lot of interesting thoughts and ideas. <a href="https://archive.org/details/kaczynski2/mode/2up">Industrial Society &amp; Its Future</a> is provocative, it&rsquo;s radical, but it&rsquo;s also quite a fascinating read. It&rsquo;s hard to understand most criminals and their motives. Sometimes I just wonder why would a person A do a terrible thing B, because it doesn&rsquo;t make sense. This book more than satisfies all the possible &ldquo;why&quot;s.</p> Crypto-Gram https://bubelov.com/blog/2020/crypto-gram/ Tue, 04 Feb 2020 00:00:00 +0000 https://bubelov.com/blog/2020/crypto-gram/ <p>Bruce Schneier is a well known security expert who writes quite a lot on many security related issues. He has a newsletter, but I&rsquo;m not a big fan of this format, so I was pretty excited to find out that Dan Henage had <a href="http://crypto-gram.libsyn.com">narrated</a> Bruce Schneier&rsquo;s newsletter for years. The link above contains a huge collection of Schneier&rsquo;s newsletters in audio format.</p> Narcos: Mexico https://bubelov.com/blog/2020/narcos-mexico/ Sun, 02 Feb 2020 00:00:00 +0000 https://bubelov.com/blog/2020/narcos-mexico/ <p>Bingeworthy, as usual. I have nothing to add, really.</p> Better Call Saul https://bubelov.com/blog/2020/better-call-saul/ Sat, 01 Feb 2020 00:00:00 +0000 https://bubelov.com/blog/2020/better-call-saul/ <p>This movie also doesn&rsquo;t need introduction and the new season started well above my expectations, which were pretty high, by the way. It&rsquo;s a pleasure to watch, but, unfortunately, Netflix didn&rsquo;t release the whole season in a single bundle, so now I have to wait for new episodes. Very frustrating practice!</p> Nextcloud Hub https://bubelov.com/blog/2020/nextcloud-hub/ Thu, 30 Jan 2020 00:00:00 +0000 https://bubelov.com/blog/2020/nextcloud-hub/ <p>Nextcloud Hub is the 18th release of Nextcloud platform. This release comes with a lot of polishing and a few new features and apps. Here is the <a href="https://youtu.be/9Sx8_9ZSWzY?t=135">announcement</a> made by Frank Karlitschek where he talks about the motivation behind Nextcloud, the need to counteract the negative trends of centralization, ways to achieve digital sovereignty, and the importance of being in control of our own digital assets:</p> <p>Nextcloud Flow is the darling of this particular release, and it looks promising. Another interesting Nextcloud app is Nextcloud Deck. I started using it a few weeks ago, and the web front-end is pretty great. I also tried its Android front-end, but it&rsquo;s not finished yet, so it&rsquo;s more like an early access version aimed at Nextcloud Deck&rsquo;s developers themselves.</p> Rust https://bubelov.com/blog/2020/rust/ Fri, 24 Jan 2020 00:00:00 +0000 https://bubelov.com/blog/2020/rust/ <p>I used to code in Java but nowadays, I don&rsquo;t use it often. Kotlin is my new language of choice, and I&rsquo;m pretty excited about it. It&rsquo;s more or less on par with C in terms of CPU efficiency, and it doesn&rsquo;t consume as much RAM as one may think. It&rsquo;s high level, strongly typed and resource efficient, is there a reason to use something else?</p> <p>Rust is not as high level as Java or Kotlin, but it doesn&rsquo;t need a virtual machine in order to run, which is important when you target resource-constrained hardware. A niche advantage? I agree, but that&rsquo;s not all. The main advantage of Rust, in my amateur opinion, is code safety. It&rsquo;s much harder to write unsafe code in Rust, and it&rsquo;s where it really shines. Rust compiler is exceptionally smart, and it goes a long way to prevent you from introducing bugs in your code.</p> <p>I worked in QA for a short time during my student years and here is a thing that stuck in my head since then: the earlier you can find a bug in your software, the less it will cost you. Bugs are expensive, and that&rsquo;s one of the reasons I&rsquo;m biased against languages that have little code quality checks. They may feel like freedom initially, but what they really do is stretching the feedback loop between introducing bugs and fixing them.</p> <p>Another interesting thing about Rust is its concept of memory ownership. JVM uses garbage collector so Java/Kotlin programmers rarely think about memory or who can access or modify certain objects.</p> <p>Here is a summary of Rust&rsquo;s memory ownership system:</p> <ul> <li>Each value in Rust has an owner.</li> <li>Values can&rsquo;t have more than one owner at a time.</li> <li>Values get &lsquo;dropped&rsquo; when their owners go out of scope.</li> </ul> <p>That&rsquo;s an interesting way to avoid having a garbage collector while not getting down to allocating and freeing up the memory, which is error-prone. Such a system allows us to write safe and performant code without a GC overhead, and it also guarantees that our threads won&rsquo;t get halted while waiting for memory to be freed up.</p> <p>Here is an interesting case study that compares Java and Rust in terms of memory consumption:</p> <p><a href="https://www.rust-lang.org/static/pdfs/Rust-Tilde-Whitepaper.pdf">https://www.rust-lang.org/static/pdfs/Rust-Tilde-Whitepaper.pdf</a></p> <p>It claims that the same code that consumed 5 GB of RAM inside JVM consumes just 50 MB after migrating to Rust. I very much doubt that, but I guess Rust can score up to 3x memory-efficiency advantage due to not using a garbage collector which might get memory-hungry under certain conditions.</p> Client-Side Encryption https://bubelov.com/blog/2020/client-side-encryption/ Tue, 21 Jan 2020 00:00:00 +0000 https://bubelov.com/blog/2020/client-side-encryption/ <p>Many apps depend on servers and there are good reasons for that. We often hear of data breaches, and it may seem like the price we have to pay for the convenience of not having to worry about losing our data. There is always a full copy, somewhere in the cloud. Sometimes, bad people can hack their way to this data, but what can we do about that?</p> <p>I was thinking about that for quite some time. The reason is: I&rsquo;m working on a portfolio tracking app which deals with really sensitive data. Is there a good reason for our target audience to share their financial data with a bunch of anons? I wouldn&rsquo;t do that, would you? I mean, a lot of people wouldn&rsquo;t mind, but we&rsquo;re also responsible for protecting that data, and it&rsquo;s not simple at all.</p> <p>Actually, there is no need to share user data with &ldquo;The Cloud&rdquo; in order to make sure it&rsquo;s available from any device, as long as users remember their auth credentials. You might have heard of the services such as ProtonMail or Tresorit, they adopted that model, and they provide their services without knowing anything about the private data of their users.</p> <p>That&rsquo;s really cool, and I hope more services will follow their lead but there is a problem with client-side encryption: it&rsquo;s hard, at least for me. I&rsquo;m a total noob in cryptography, but I feel that it&rsquo;s the only way forward. So, I&rsquo;ve found an interesting <a href="https://medium.com/passpill-project/creating-a-client-side-encryption-system-aaa601b4ad35">article</a> to start with. I&rsquo;ll play with this scheme and, hopefully, implement it in our app, so we won&rsquo;t know anything about the financial data of our users. No data, no problem.</p> RSS Tricks https://bubelov.com/blog/2020/rss-tricks/ Thu, 16 Jan 2020 00:00:00 +0000 https://bubelov.com/blog/2020/rss-tricks/ <p>RSS is a great way to subscribe to various blogs, but it can be used for a lot of other things. For instance, you can get notified of new releases of any GitHub project right from your RSS feed. Here is an example:</p> <p><a href="https://github.com/rust-lang/rust/releases.atom">https://github.com/rust-lang/rust/releases.atom</a></p> <p>It&rsquo;s also possible to add your favorite YouTube channels to your RSS feed. For example, here is a link to a <a href="https://www.youtube.com/feeds/videos.xml?channel_id=UCZEPItn2Nb62Zso5eohHAAA">BBC Documentary feed</a>.</p> <p>So, YouTube feed links have the following format:</p> <pre tabindex="0"><code>https://www.youtube.com/feeds/videos.xml?channel_id=&lt;id&gt; </code></pre><p>BBC Documentary&rsquo;s <code>channel_id</code> is <code>UCZEPItn2Nb62Zso5eohHAAA</code> and you can get the ID&rsquo;s of your favorite channels by inspecting your address bar when they are open.</p> Thunderbolt 4 and USB 4 https://bubelov.com/blog/2020/thunderbolt-4-usb-4/ Mon, 13 Jan 2020 00:00:00 +0000 https://bubelov.com/blog/2020/thunderbolt-4-usb-4/ <p>Thunderbolt 3 is an interface that is available on pretty much every high-end laptop nowadays. It&rsquo;s almost too good to be true. Thunderbolt-enabled hardware have a set of unified USB ports that can be connected to up to four PCIe 3.0 lanes. One such lane gives us up to 8 GT/s (985 MB/s), so it should give us up to 32 GT/s, although Intel claims that it&rsquo;s capable of 40 GT/s. I&rsquo;m not sure where those extra 8 GT/s come from, but many people claim that their Thunderbolt 3 devices are only capable of ~22 GT/s. Anyway, it&rsquo;s crazy fast!</p> <p>Fast transfer speed is not the only benefit of Thunderbolt 3, it also allows us to charge our devices or send video output to external monitors. It&rsquo;s also possible to use an external GPU and even Ethernet over Thunderbolt. Think of a small GPU enclosure that you can connect to your laptop with a single USB cable and aside from being able to do many GPU-intensive tasks, it would also keep your laptop charged and route your traffic via a wired interface which may improve your network throughput and latency. It&rsquo;s a truly amazing little port.</p> <p>So, on CES 2020 keynote, Intel announced a new generation of Thunderbolt: Thunderbolt 4, which left many people excited and confused at the same time. Intel promised some speed improvements, so it&rsquo;s supposed to be 4 times faster than&hellip; USB 3.2. 2x1. Well, that was unexpected. Why do they compare Thunderbolt 4 to USB when they have Thunderbolt 3 as a more clear point of reference? They announced that Thunderbolt 4 is 4 times faster than USB 3.2. 2x1, which means 40 GT/s, same speed as Thunderbolt 3. That&rsquo;s very strange, maybe they&rsquo;ll clarify things later.</p> <p>Aside from Thunderbolt standards, now we also have USB4 out in the wild. It&rsquo;s practically the same thing as Thunderbolt 3, nothing new and exciting, but at least it&rsquo;s not semi-proprietary which is nice. It looks like we&rsquo;re going to stick with 40 GT/s for a while.</p> <p>Actually, I expected that Intel would use the full potential of PCIe 4.0 which doubles the bandwidth to 16 GT/s (1970 MB/s) per every lane. A man can dream.</p> The Sovereign Individual https://bubelov.com/blog/2020/sovereign-individual/ Fri, 10 Jan 2020 00:00:00 +0000 https://bubelov.com/blog/2020/sovereign-individual/ <p>This is a very controversial book, but it&rsquo;s one of the things that makes it interesting. It made me <del>Google</del> DuckDuckGo both of its authors, and I wasn&rsquo;t disappointed, to say the least. Some ideas of this book are shocking, but it doesn&rsquo;t make them false. <a href="https://www.simonandschuster.com/books/The-Sovereign-Individual/James-Dale-Davidson/9781439144732">The Sovereign Individual</a> is a fascinating read if you like slightly dystopian futurology at least as much as I do.</p> Wi-Fi vs Wired Networking on Raspberry Pi 4 https://bubelov.com/blog/2020/raspberry-pi-4-wifi-vs-wired/ Mon, 06 Jan 2020 00:00:00 +0000 https://bubelov.com/blog/2020/raspberry-pi-4-wifi-vs-wired/ <p>I made a few tests to find out how to boost network transfer speed on Raspberry Pi 4 and is it worth the hassle.</p> <p><figure> <a href="https://bubelov.com/blog/2020/raspberry-pi-4-wifi-vs-wired/thumb_hu_ae31b57f54588bed.webp"> <img src="https://bubelov.com/blog/2020/raspberry-pi-4-wifi-vs-wired/thumb_hu_bb70513815eda101.webp" alt="" /> </a> </figure></p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#tldr">TL;DR</a></li> <li><a href="#preface">Preface</a></li> <li><a href="#test-structure">Test Structure</a></li> <li><a href="#generating-test-data">Generating Test Data</a></li> <li><a href="#testing-data-throughput-over-wi-fi-and-ethernet">Testing Data Throughput Over Wi-Fi and Ethernet</a></li> <li><a href="#results">Results</a></li> <li><a href="#testing-raspberry-pi-4-throughput-with-iperf">Testing Raspberry Pi 4 Throughput With iPerf</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="tldr">TL;DR</h2> <p>Prefer wired connections when it&rsquo;s not too inconvenient. For maximum data throughput, you should also use an SSD because regular SD cards won&rsquo;t let you fully utilize the potential of your wired connection.</p> <h2 id="preface">Preface</h2> <p>Raspberry Pi 4 is a capable SBC which comes with two built-in network interfaces: <a href="https://en.wikipedia.org/wiki/Gigabit_Ethernet">Gigabit Ethernet</a> and <a href="https://en.wikipedia.org/wiki/IEEE_802.11ac-2013">IEEE 802.11ac Wi-Fi</a>. Having options is good, but it&rsquo;s also a common source of confusion. Which interface should we use and why?</p> <p>In this article, I compare wired and Wi-Fi interfaces in terms of their maximum data throughput. If you&rsquo;re new to networking, you might also benefit from reading about network latency and its role in the performance of networked software. In short, wired connections usually have an order of magnitude better latency than the wireless ones and that might be more important than the amount of megabytes you can push per second. That said, let&rsquo;s do some tests!</p> <p>First, it&rsquo;s good to keep in mind that we can&rsquo;t trust the benchmarks completely. Real-world performance can depend on many factors, and it&rsquo;s always a good idea to reproduce the same tests with your own hardware and in your own environment.</p> <h2 id="test-structure">Test Structure</h2> <p>Here is what my test does:</p> <ul> <li>Generate 1 GB of random data on my laptop</li> <li>Send this data to my Raspberry Pi and measure the average speed</li> <li>Fetch this data from my Raspberry Pi and measure the average speed</li> </ul> <p>I repeated this test with four different configurations:</p> <ol> <li>Raspberry Pi that uses Wi-Fi and stores data on SD card</li> <li>Raspberry Pi that uses Wi-Fi and stores data on SSD drive</li> <li>Raspberry Pi that uses Ethernet and stores data on SD card</li> <li>Raspberry Pi that uses Ethernet and stores data on SSD drive</li> </ol> <p>That&rsquo;s it. Now, it&rsquo;s time to generate that random data.</p> <h2 id="generating-test-data">Generating Test Data</h2> <p>We can use <code>dd</code> to generate a new file and <code>/dev/urandom</code> is a great data source:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">dd if=/dev/urandom of=big.file bs=64M count=16 iflag=fullblock </span></span></code></pre></div><p>Let&rsquo;s clarify these arguments and their purpose:</p> <ul> <li><code>if</code> - input file. In our case, we attach our input to the stream of random data which is available on any Linux distribution.</li> <li><code>of</code> - output file. It&rsquo;s a name of the file we want to create. Feel free to use any name you want.</li> <li><code>bs</code> - in our case, it means that we want to generate our random file piece by piece and there should be <code>count</code> pieces in it. It also means that we can tweak both arguments, the only important thing is that <code>bs</code> * <code>count</code> should be close to our target size (1 GB).</li> <li><code>count</code> - as mentioned above, it&rsquo;s just a multiplier for <code>bs</code></li> </ul> <h2 id="testing-data-throughput-over-wi-fi-and-ethernet">Testing Data Throughput Over Wi-Fi and Ethernet</h2> <p>Let&rsquo;s send some data to Raspberry Pi (<code>Laptop --&gt; Pi</code>):</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">rsync --progress big.file [email protected]:~/big.file </span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">big.file </span></span><span class="line"><span class="cl"> 1,073,741,824 100% 32.10MB/s 0:00:31 (xfr#1, to-chk=0/1) </span></span></code></pre></div><p>I used <code>rsync</code> because it&rsquo;s easy to use, and it reflects my real workloads (syncing a lot of files, doing backups and so on).</p> <p>Now we can download that big file from the Raspberry Pi and measure the average transfer speed (<code>Laptop &lt;-- Pi</code>):</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">rsync --progress [email protected]:~/big.file big.file.back </span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">big.file </span></span><span class="line"><span class="cl"> 1,073,741,824 100% 42.69MB/s 0:00:23 (xfr#1, to-chk=0/1) </span></span></code></pre></div><p>That&rsquo;s it. I repeated this test for each of tested setups and here are the results:</p> <h2 id="results">Results</h2> <table> <thead> <tr> <th>From</th> <th>To</th> <th>Interface</th> <th>Storage</th> <th>Speed (Mbit/s)</th> <th>Speed (MB/s)</th> </tr> </thead> <tbody> <tr> <td>Dell XPS 13 (2018)</td> <td>Raspberry Pi 4</td> <td>Wi-Fi</td> <td>SD</td> <td>58.24</td> <td>7.28</td> </tr> <tr> <td>Dell XPS 13 (2018)</td> <td>Raspberry Pi 4</td> <td>Wi-Fi</td> <td>SSD</td> <td>55.84</td> <td>6.98</td> </tr> <tr> <td>Dell XPS 13 (2018)</td> <td>Raspberry Pi 4</td> <td>Gigabit Ethernet</td> <td>SD</td> <td>159.60</td> <td>19.95</td> </tr> <tr> <td>Dell XPS 13 (2018)</td> <td>Raspberry Pi 4</td> <td>Gigabit Ethernet</td> <td>SSD</td> <td>340.56</td> <td>42.57</td> </tr> <tr> <td>Raspberry Pi 4</td> <td>Dell XPS 13 (2018)</td> <td>Wi-Fi</td> <td>SD</td> <td>80.72</td> <td>10.09</td> </tr> <tr> <td>Raspberry Pi 4</td> <td>Dell XPS 13 (2018)</td> <td>Wi-Fi</td> <td>SSD</td> <td>82.00</td> <td>10.25</td> </tr> <tr> <td>Raspberry Pi 4</td> <td>Dell XPS 13 (2018)</td> <td>Gigabit Ethernet</td> <td>SD</td> <td>339.20</td> <td>42.40</td> </tr> <tr> <td>Raspberry Pi 4</td> <td>Dell XPS 13 (2018)</td> <td>Gigabit Ethernet</td> <td>SSD</td> <td>341.52</td> <td>42.69</td> </tr> </tbody> </table> <p>Interesting results, but let&rsquo;s try an alternative approach to measuring network throughput before jumping to any conclusions.</p> <h2 id="testing-raspberry-pi-4-throughput-with-iperf">Testing Raspberry Pi 4 Throughput With iPerf</h2> <p>We can easily measure &ldquo;raw&rdquo; network performance by moving as much data as possible between two computers for a certain period of time. One of those computers has to play a role of a client by generating a lot of gibberish and sending it to the second machine which can simply discard all of that traffic upon arrival.</p> <p>There are many ways to perform such a test but <code>iperf</code> is a kind of industry standard, and it&rsquo;s very easy to use, which makes it a good choice for our quick test.</p> <p>Let&rsquo;s make Raspberry Pi 4 a server:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">ssh [email protected] </span></span><span class="line"><span class="cl">sudo apt install iperf </span></span><span class="line"><span class="cl">iperf --server </span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Server listening on TCP port 5001 </span></span><span class="line"><span class="cl">TCP window size: 85.3 KByte (default) </span></span></code></pre></div><p>Now, we can use <code>iperf</code> from another computer and instruct it to connect to a Raspberry Pi in order to measure the network throughput between them:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">sudo apt install iperf </span></span><span class="line"><span class="cl">iperf --client pi.local </span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Client connecting to cloud.local, TCP port 5001 </span></span><span class="line"><span class="cl">TCP window size: 85.0 KByte (default) </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl">[ ID] Interval Transfer Bandwidth </span></span><span class="line"><span class="cl">[ 3] 0.0-10.0 sec 1.10 GBytes 942 Mbits/sec </span></span></code></pre></div><p>Here are my results for wired and Wi-Fi connections:</p> <table> <thead> <tr> <th>Interface</th> <th>Speed (Mbit/s)</th> </tr> </thead> <tbody> <tr> <td>Gigabit Ethernet</td> <td>942</td> </tr> <tr> <td>Wi-Fi (IEEE 802.11ac)</td> <td>86</td> </tr> </tbody> </table> <p>Seems like both tests point to the same conclusion.</p> <h2 id="conclusion">Conclusion</h2> <p>Although using Wi-Fi is more convenient because it gives you more flexibility, don&rsquo;t expect your network transfer speed to be anywhere above 86 Mbit/s if you connect your Raspberry Pi wirelessly. 86 Mbit/s is actually not that bad if you use an SD card because many modern SD cards can&rsquo;t go beyond ~80 Mbit/s when they write data, although they can read a bit faster and not being able to use this extra reading speed is the main drawback of using Wi-Fi on a Raspberry Pi 4.</p> <p>Things get a bit different if you prefer to store your data on an SSD drive. Modern solid state drives can move gigabytes in seconds, but your Wi-Fi connection wouldn&rsquo;t allow you to use any that extra throughput. Switching to a wired connection boosts transfer speed up to about 30-40 MB/s when using <code>rsync</code>, which is better, but still a far cry from the theoretical limit of 125 MB/s (that&rsquo;s the max speed of a Gigabit Ethernet adapter that is used in Raspberry Pi 4). It might be due an encryption overhead which puts some pressure on a CPU, making it a bottleneck. That&rsquo;s precisely why benchmarking is hard, there are many components that might affect the results.</p> Command Line Heroes https://bubelov.com/blog/2020/cli-heroes/ Sat, 04 Jan 2020 00:00:00 +0000 https://bubelov.com/blog/2020/cli-heroes/ <p><a href="https://www.redhat.com/en/command-line-heroes">This podcast</a> might be interesting for people who&rsquo;re into history of computing and who likes to listen to people talking about the new trends in IT. I&rsquo;ve learned a few interesting facts about those matters, and it&rsquo;s always a pleasure to listen to a well-made podcast.</p> 2019 in Retrospect https://bubelov.com/blog/2019/2019-in-retrospect/ Mon, 30 Dec 2019 00:00:00 +0000 https://bubelov.com/blog/2019/2019-in-retrospect/ <p>I started a new project and had the chance to work with some exciting tools and technologies. Outside of work, I took a few road trips across Thailand, visited several cities in Russia, and spent a week in Spain, which, I have to say, was the nicest of all.</p> <p>On the tech side, I&rsquo;ve fully switched to Linux, and almost all the software I use is now open source. I also put significant effort into cutting Google and Apple out of my life. It wasn&rsquo;t easy, but it&rsquo;s been completely worth it.</p> <p>Lately, I&rsquo;ve been thinking a lot about individual sovereignty and the tools that can help us reclaim it. These are global issues, and thoughts alone don&rsquo;t change reality, so it makes more sense to focus on small, clearly solvable pieces of those bigger problems. That&rsquo;s what I plan to keep doing in the year ahead.</p> <p>I don&rsquo;t like plans and commitments, especially public ones, so I&rsquo;ll refrain from them, but I have a few wishes. I have no power to make them true, but I&rsquo;ll do my part, where possible:</p> <ul> <li>It would be nice to see a cleaner and faster web without countless trackers and ads.</li> <li>More visa-free countries and more ways to stay in different countries for longer than a usual vacation, please.</li> <li>Woldn&rsquo;t it be great for people to be less dependent on nasty intermediaries? Our financial system is too centralized, and I hope that we&rsquo;ll see <a href="https://bitcoinops.org/en/newsletters/2019/12/28/">some traction</a> on the cryptocurrency front. In the ideal world, our financial data should be private and in our total control, and we should be able to authorize our transactions by ourselves. It&rsquo;s not the case now, unfortunately, and I have to rely on my bank and my broker. I consider such a development a logical next step in order to take back control of our data.</li> <li>Less JavaScript. Generally, and especially on mobile.</li> <li>More progress on efficient tools that respect both programmers and machines.</li> <li>Less Google Service dependencies in Android apps. Google Play isn&rsquo;t the only store around!</li> </ul> Hugo https://bubelov.com/blog/2019/hugo/ Wed, 25 Dec 2019 00:00:00 +0000 https://bubelov.com/blog/2019/hugo/ <p>Hugo is an open-source static site generator and the more I use it, the more I like it. Unfortunately, my first choice was Jekyll, a Ruby-based generator, and it was terribly slow and hard to work with. Switching to Hugo decreased site compilation time from 14 seconds to 14 milliseconds, but I expected Hugo to slow down as I add more features, but it&rsquo;s still blazing fast.</p> <p>Hugo is a poster child of a modern open source project. Seriously, it&rsquo;s everything one can dream of and far, far beyond. Easy to use? Checked. Free? Of course. Well maintained and constantly evolving? No doubt about that. Their changelogs and documentation is a pleasure to read, a rare property for a piece of software, unfortunately.</p> Own Your Data https://bubelov.com/blog/2019/own-your-data/ Wed, 25 Dec 2019 00:00:00 +0000 https://bubelov.com/blog/2019/own-your-data/ <p>Taking control of my personal data was one of my biggest priorities in 2019. The rise of cloud computing and the centralization of the web pose real threats, not just to our economy, but to our personal security.</p> <p>We have to remember that other actors will always prioritize their own interests over ours. Sometimes it&rsquo;s a zero‑sum game (even though honest business shouldn&rsquo;t be), and a few bad actors end up casting a shadow over everyone else in the market.</p> <p>All that marketing spin about &ldquo;needing your data for your own good&rdquo; is one of the biggest lies in modern history.</p> <p>We can clearly see the huge demand for giving people control over their data, and times are finally changing. Look at the GDPR, the California Consumer Privacy Act, and other legislative moves to rein in the damage done by data‑hungry tech companies, damage often done without clear user consent (and no, a 100‑page user agreement isn&rsquo;t &ldquo;consent&rdquo;).</p> <p>I believe things will get better in the future, but we don&rsquo;t have to wait. It&rsquo;s entirely possible to take back control of most of our data now, thanks to cheap hardware and free, verifiable software.</p> <h2 id="raspberry-pi-4">Raspberry Pi 4</h2> <p><figure> <a href="https://bubelov.com/blog/2019/own-your-data/raspberry-pi-4_hu_1b4d9fe52c7a6805.webp"> <img src="https://bubelov.com/blog/2019/own-your-data/raspberry-pi-4_hu_47bc6fec24cb5d3a.webp" alt="" /> </a> <figcaption>Raspberry Pi 4</figcaption> </figure></p> <p>This little beast costs $35 and can do a surprising amount, including serving as a personal data hub. It&rsquo;s also a perfect learning tool for anyone curious about Linux but not ready to install it on their main machine. With a Raspberry Pi, you can experiment with a full Linux computer without worrying about driver issues, spotty documentation, or other typical Linux hurdles.</p> <h2 id="nextcloud">Nextcloud</h2> <p><a href="https://nextcloud.com/">Nextcloud</a> is a great platform for hosting your own data. You don&rsquo;t need to give Google your contacts, Nextcloud can store them. You also don&rsquo;t need Dropbox or Google Drive, you can do the same things with Nextcloud for a fraction of the cost, while keeping full control of your files.</p> <p>It comes with a gallery app, an RSS reader, an instant messenger, and plenty of other tools that normally require handing your data to third parties. It&rsquo;s a powerful, self‑hosted alternative that can replace most of Google&rsquo;s services without sacrificing your privacy or security.</p> LineageOS https://bubelov.com/blog/2019/lineage/ Mon, 23 Dec 2019 00:00:00 +0000 https://bubelov.com/blog/2019/lineage/ <p>Google is an interesting beast. It doesn&rsquo;t sell crappy hardware while blindly denying it&rsquo;s defects, and it once had a motto which said &ldquo;Don&rsquo;t be evil&rdquo;, although they changed their mind and ditched it a few years ago. Their way to make money is quite clever and also insidious. They seem to be everywhere, giving away free stuff like Santa Claus. Do you need email? Searching for something? Maybe you want to use maps? Where would you go to do most of your tasks online? Google controls a huge chunk of the modern web, and it follows and tracks you everywhere in order to sell targeted ads to their clients. And who&rsquo;s the target? Right&hellip;</p> <p>The problem is, it&rsquo;s not that easy to remove Google from our lives. It&rsquo;s pretty easy to switch from an iPhone to a hyped new Pixel but it&rsquo;s just choosing between two evils. Unfortunately, ~80% of mobile phones are controlled by Google and the rest of them are controlled by Apple. There are no alternative mainstream options.</p> <p>So I thought. I mean, it&rsquo;s true, but I work in tech, so it doesn&rsquo;t have to be mainstream, and I can tolerate a bit of complexity and read a guide or two. The obvious non-mainstream alternative is LineageOS, the successor to CyanogenMod, so I tried it, and I&rsquo;ve never came back to Google ROMs, so it looks like I managed to ditch both Google and Apple this year, what a great year it was!</p> <p>LineageOS team is doing a great job, and they would probably appreciate any financial contributions. I don&rsquo;t have time to write code for this project, so I started <a href="https://patreon.com/LineageOS">supporting them on Patreon</a>.</p> Linux https://bubelov.com/blog/2019/linux/ Mon, 23 Dec 2019 00:00:00 +0000 https://bubelov.com/blog/2019/linux/ <p>Ironically, one of the best things that happened to me this year was my MacBook drowning in a cup of coffee. Totally my fault, but the broken arrow key that followed wasn&rsquo;t. It turns out the official service center won&rsquo;t replace a single key, they&rsquo;d only do a full $2,000 logic‑board repair. Well, fuck Apple.</p> <p>I found a local unofficial shop and fixed it for a fraction of the price, but the whole mess left me thinking: with a MacBook, I&rsquo;m not really in control of my own hardware, or software.</p> <p>A couple of days later, I decided I shouldn&rsquo;t encourage that kind of behavior. It was time for a better laptop. In fact, more conscious consumption was one of my main themes for 2019. When we tolerate crap in the products we buy, we signal to our peers that it&rsquo;s okay, and we tell companies they can get away with shady practices, like treating every piece of software as a data‑harvesting tool until proven otherwise.</p> <p>I settled on a Dell XPS 13 and have been using it exclusively since the summer of 2019. That means Linux, so I&rsquo;m finally Apple‑free.</p> Butterick’s Practical Typography https://bubelov.com/blog/2019/typography/ Sun, 15 Dec 2019 00:00:00 +0000 https://bubelov.com/blog/2019/typography/ <p>I discovered a great, practical guide to typography, and now I can&rsquo;t stop noticing how important fonts and text sizing really are. The author sells some high-end fonts (starting at $100+), but he also recommends several excellent free alternatives.</p> <p>What&rsquo;s really interesting is his business model: he claims to make over $10,000 a year from this free book. That&rsquo;s pretty cool, I didn&rsquo;t think that was possible, especially when even paid books often struggle to cover their costs.</p> <p><a href="https://practicaltypography.com/">https://practicaltypography.com/</a></p> Free to Choose https://bubelov.com/blog/2019/free-to-choose/ Wed, 11 Dec 2019 00:00:00 +0000 https://bubelov.com/blog/2019/free-to-choose/ <p>I&rsquo;ve come across a series of videos called &ldquo;Free to Choose&rdquo; featuring Milton Friedman. That&rsquo;s a good set of videos where Friedman explains his brilliant ideas and then discusses them with different public figures. His verbal skills are fascinating, and I&rsquo;ve never seen an economist who can explain complex things in such an easy and fun way as Mr. Friedman.</p> The Witcher https://bubelov.com/blog/2019/witcher/ Wed, 11 Dec 2019 00:00:00 +0000 https://bubelov.com/blog/2019/witcher/ <p>I couldn&rsquo;t ignore the hype around The Witcher on Netflix, so I watched it, and it&rsquo;s a great movie. It&rsquo;s hard to escape comparison with Game of Thrones, but I find The Witcher more fun and less dramatic. I mean, I liked the beginning of game of Thrones, but I couldn&rsquo;t bear it after season 4 due to its extreme dullness.</p> On Debt https://bubelov.com/blog/2019/debt/ Mon, 09 Dec 2019 00:00:00 +0000 https://bubelov.com/blog/2019/debt/ <p>Debt is a fascinating concept, and it has a huge impact on our lives whether we like it or not.</p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#debt-and-society">Debt and Society</a></li> <li><a href="#its-not-only-about-money">It&rsquo;s Not Only About Money</a></li> <li><a href="#debt-to-ourselves">Debt to Ourselves</a></li> <li><a href="#getting-rid-of-debt">Getting Rid of Debt</a></li> <li><a href="#shifting-focus">Shifting Focus</a></li> <li><a href="#examples">Examples</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="debt-and-society">Debt and Society</h2> <p>I tend to avoid financial debt unless it has zero interest, and I&rsquo;m horrified of things such as credit cards and other predatory tools of lending that exacerbate inequality. Of course, abstaining from personal debt is generally good for you, but we don&rsquo;t really have a say in how much our governments borrows on our behalf, and it can backfire at any moment.</p> <p>Most of the economic downturns we faced in the past were caused by an excessive debt and the same scenario will probably keep repeating. I guess it&rsquo;s safe to say that the financial debt is just a single manifestation of a broader tendency: it seems that we would often prefer more &ldquo;good&rdquo; things happen to us now even if it damaged us in the long run.</p> <h2 id="its-not-only-about-money">It&rsquo;s Not Only About Money</h2> <p>Debt is everywhere. An outsider might be surprised how often you hear the word &ldquo;debt&rdquo; in casual conversations between software engineers. That’s <strong>technical debt</strong>, compromising software quality to ship something faster. It’s a big reason many apps feel so unstable and half‑baked.</p> <p>As with financial debt, views on technical debt are split. Some see it as a necessary tool while others avoid it at all costs. There are also &ldquo;debt junkies&rdquo; and &ldquo;perfectionists&rdquo; camped at the extremes, and both extremes usually lead to disastrous long‑term outcomes.</p> <p>Personally, I wouldn&rsquo;t sleep well with financial debt hanging over me, but I&rsquo;m okay with a certain level of technical debt. The difference is significant: <strong>you&rsquo;re not contractually obligated to pay technical debt back</strong>. There&rsquo;s no repo man for sloppy code.</p> <p>People who stress too much about technical debt often tie their ego to their code, which is generally a bad practice. Let&rsquo;s be honest: the main source of technical debt is the business side, tight deadlines and budget constraints. You don&rsquo;t have to take it personally. Sometimes it makes perfect sense to show a rough proof‑of‑concept to a potential investor and keep the lights on, rather than going bankrupt perfecting a product no one asked for.</p> <h2 id="debt-to-ourselves">Debt to Ourselves</h2> <p>It&rsquo;s dangerous to overestimate our ability to pay back debt. When personal debt spirals out of control, the stress is immense. But even commitments with no external repercussions, the ones we make only to ourselves, can be a heavy mental burden.</p> <p>Every time we tell our future selves to quit a bad habit, get fit, read more, or achieve any personal goal, we take on a kind of psychological debt. If we announce that commitment publicly, the pressure, and the potential for guilt, only grows.</p> <p>Opinions on this are split. Some believe public commitments are a powerful motivator. Others see them as a form of self‑imposed poison. Personally, I lean toward the latter view.</p> <h2 id="getting-rid-of-debt">Getting Rid of Debt</h2> <p>I care a lot about promises given, whether I give them to myself or to someone else. I used to expect the same from everyone else but it turned out to be a bad idea, but that&rsquo;s a different story.</p> <p>It&rsquo;s pretty easy when it comes to financial debt: we can deal with this issue by paying it back and not taking on more. Yes, it means spending less than you can, but I never had any issues with that, and I believe that the urge to spend everything you have and even more might be a problem worth solving as early in your life as possible.</p> <p>It&rsquo;s much harder when it comes to the promises we make to ourselves and I burned-out a few times trying to bite off more than I can chew. I used to set big goals and rough deadlines, and to achieve them by all means necessary. Looking back, I think it wasn&rsquo;t always the best strategy. The same approach of not taking on new debt and paying back the old one might work with this kind of debt too. In theory, it&rsquo;s even easier: no one forces us to pay back the old debt except our own selves. Unfortunately, we can be our worst enemies and it can be really tough to negotiate any &ldquo;debt reduction&rdquo; with our own selves.</p> <h2 id="shifting-focus">Shifting Focus</h2> <p>What worked for me is shifting focus from the exact goals and deadlines to continuous progress. It&rsquo;s tempting to procrastinate for a while and then to do everything &ldquo;the night before the exam&rdquo; but it&rsquo;s always a gamble and, as any kind of gamble, it comes with a lot of stress and uncertainty. It seems pretty obvious, but I can&rsquo;t stop seeing those patterns of behavior on every level from individuals to nuclear superpowers.</p> <p>Many people say they expected more &ldquo;pleasure&rdquo; from achieving their big goals. I guess it makes sense, there is no &ldquo;high&rdquo; that can last forever, but I find that setting small goals and achieving them every day is more rewarding than focusing on something big or pushing yourself to meet unrealistic or excessive deadlines. It&rsquo;s just more fun to feel the progress every day and to take pride in that than to push yourself to the limit and to burn-out later.</p> <h2 id="examples">Examples</h2> <p>I use this approach for anything that doesn&rsquo;t have critical deadlines and I&rsquo;m satisfied with the results so far:</p> <ul> <li> <p><strong>Learning</strong>. It&rsquo;s not that hard to learn a single new thing about the topics that interest me. Focusing on a short and isolated topic also helps to understand things on a deeper level.</p> </li> <li> <p><strong>Personal Projects</strong>. I have a few personal projects (including this website) and limited time so I can&rsquo;t put aside everything else and focus on them full time, but it&rsquo;s not an excuse for abandoning any of them. Consistently adding small improvements helps me not to feel that I abandoned my long-term commitments.</p> </li> <li> <p><strong>Housekeeping</strong>. I like to start my day with listening to a podcast and doing some housekeeping while it plays. This way, my &ldquo;housekeeping debt&rdquo; do not pile up, so I don&rsquo;t have to spend a few hours on Saturdays doing something not very pleasurable. Also, listening to podcasts improves my English, which also amounts to something if you consider that there are 365 mornings in a single year.</p> </li> </ul> <h2 id="conclusion">Conclusion</h2> <p>Debt comes in many forms, some nastier than others. While &ldquo;avoid all debt&rdquo; is sound personal finance advice, what about the promises we make to ourselves?</p> <p>The key is to be realistic, don&rsquo;t demand too much of your future self, but demanding nothing isn&rsquo;t an option either (unless you&rsquo;re a Buddhist saint or an extreme nihilist). It&rsquo;s fine to have goals and the will to reach them, but it helps to focus on the <strong>process</strong> rather than the goal itself.</p> <p>When it comes to long‑term commitments, I believe consistency beats a single, concentrated effort every time.</p> Hotline Miami 2 https://bubelov.com/blog/2019/hotline-miami-2/ Sat, 07 Dec 2019 00:00:00 +0000 https://bubelov.com/blog/2019/hotline-miami-2/ <p>Perfect, just perfect. Hotline Miami 2: Wrong Number feels like the incremental improvement over the first game that I was hoping for. The story is sharper, the weapons are more plentiful, and the gameplay is even more fun and challenging.</p> <p>Call me a heretic, but I&rsquo;d love to see some RPG elements and maybe even an open world in the next game of the series.</p> <p>An interesting random fact: this ultraviolent, hyper-stylized game was built with GameMaker Studio, an engine originally designed for education and aimed at helping kids learn to program. I used it as a kid myself, it was a fantastic tool for getting started. It&rsquo;s pretty wild that such a gritty, intense game came from a beginner-friendly engine.</p> Nex Machina https://bubelov.com/blog/2019/nex-machina/ Wed, 04 Dec 2019 00:00:00 +0000 https://bubelov.com/blog/2019/nex-machina/ <p>That&rsquo;s a nice arcade style game that&rsquo;s perfect for anyone who has a couple of free evenings and who&rsquo;s not ready to invest a ton of time in completing a long game. It feels a bit retro, and it doesn&rsquo;t need a 100-page manual as most AAA games tend to do. Just run, shoot and have fun.</p> Picking a Suitable File System for Backups https://bubelov.com/blog/2019/backup-fs/ Sat, 30 Nov 2019 00:00:00 +0000 https://bubelov.com/blog/2019/backup-fs/ <p>Backups are crucial, especially if you prefer to store your data without third‑party &ldquo;help&rdquo;. A fast SSD makes the whole process much quicker, so I bought SanDisk Extreme portable SSD. It works great, a huge upgrade from my old hard drive.</p> <p>Was it the best option? Not necessarily. Looking back, it probably would have been cheaper to buy an M.2 SSD and a USB enclosure separately. That route gives you more choice and better value for the same performance.</p> <p>I plan to try that DIY option next, since I need another external drive anyway.</p> <p>Picking decent storage isn&rsquo;t enough, choosing the right file system can be a headache, too. I like ext4, and it works perfectly on my Linux laptop. But my backups also need to be readable on Windows and macOS, which rules ext4 out.</p> <p>So, what&rsquo;s left? There are two obvious candidates:</p> <ul> <li><strong>exFAT</strong></li> <li><strong>NTFS</strong></li> </ul> <p>exFAT wins on universal compatibility. NTFS, however, is much more modern and packs useful features like journaling. It&rsquo;s also better supported than you might think: it works natively on Windows and runs well on Linux thanks to NTFS‑3G.</p> <p>The only real catch is macOS: it can <strong>read</strong> NTFS volumes fine, but it can&rsquo;t <strong>write</strong> to them by default. Since I don&rsquo;t plan to write backup data from a Mac, I&rsquo;ve gone with NTFS.</p> The Issue With A2 SD Cards https://bubelov.com/blog/2019/a2-sd/ Sat, 16 Nov 2019 00:00:00 +0000 https://bubelov.com/blog/2019/a2-sd/ <p>Keep in mind that A2 SD card manufacturers aren&rsquo;t always fully honest in their marketing. They can promise blazing‑fast speeds, but that performance is far from guaranteed. The problem is that A2 isn&rsquo;t as &ldquo;self‑contained&rdquo; as A1 and its speed depends heavily on the host device.</p> <p>Many newer hardware and software components simply aren&rsquo;t ready to utilize the A2 standard. For instance, there are no measurable benefits to using an A2 card on a Raspberry Pi 4, despite the board and its OS being just a few months old. You could easily end up overpaying for performance you&rsquo;ll never actually get. <a href="https://www.jeffgeerling.com/blog/2019/a2-class-microsd-cards-offer-no-better-performance-raspberry-pi">Here&rsquo;s more on the issue</a>.</p> Fallout: New Vegas https://bubelov.com/blog/2019/fallout-new-vegas/ Mon, 11 Nov 2019 00:00:00 +0000 https://bubelov.com/blog/2019/fallout-new-vegas/ <p>I&rsquo;ve been a fan of the Fallout series for a while, but Fallout 3 came out at the wrong time for me, 2008, when I was deep into computer science studies and looking for my first job in the field.</p> <p>I played Fallout 4 when it launched in late 2015, and I&rsquo;m a longtime fan of Fallout 1 &amp; 2, so there was no way I could skip the third mainline game forever. The original Fallout 3 by Bethesda had a mixed reception, so I opted instead for the spin-off developed by Obsidian Entertainment: Fallout: New Vegas.</p> <p>The game is great. I haven&rsquo;t finished it yet, but I&rsquo;d definitely recommend it to anyone who likes open‑world, post‑apocalyptic RPGs.</p> Detecting Fake Flash Drives and SD cards https://bubelov.com/blog/2019/fake-flash/ Thu, 07 Nov 2019 00:00:00 +0000 https://bubelov.com/blog/2019/fake-flash/ <p>How to test SD cards and flash drives in order to make sure they deliver on their promises.</p> <p><figure> <a href="https://bubelov.com/blog/2019/fake-flash/thumb_hu_420944eb9421873c.webp"> <img src="https://bubelov.com/blog/2019/fake-flash/thumb_hu_6fd397c867a91ba8.webp" alt="" /> </a> <figcaption>Illustration by <a href="https://unsplash.com/@tompumford">Tom Pumford</a></figcaption> </figure></p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#preface">Preface</a></li> <li><a href="#fraud-is-everywhere">Fraud is Everywhere</a></li> <li><a href="#fight-flash-fraud">Fight Flash Fraud</a></li> <li><a href="#installing-f3">Installing F3</a></li> <li><a href="#using-f3">Using F3</a></li> <li><a href="#testing-different-sd-cards">Testing different SD cards</a> <ul> <li><a href="#card-1-sandisk-ultra-c10-16-gb-2012">Card 1: SanDisk Ultra C10 16 GB (2012)</a></li> <li><a href="#card-2-sandisk-ultra-c10-16-gb-2015">Card 2: SanDisk Ultra C10 16 GB (2015)</a></li> <li><a href="#card-3-samsung-evo-plus-u3-128-gb-2017">Card 3: Samsung EVO Plus U3 128 GB (2017)</a></li> <li><a href="#card-4-sandisk-edge-c10-a1-16-gb-raspberry-pi-4-desktop-kit-default-2019">Card 4: SanDisk EDGE C10 A1 16 GB (Raspberry Pi 4 Desktop Kit Default, 2019)</a></li> <li><a href="#card-5-sandisk-ultra-c10-a1-64-gb-2019">Card 5: SanDisk Ultra C10 A1 64 GB (2019)</a></li> </ul> </li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="preface">Preface</h2> <p>It&rsquo;s not uncommon to see myriads of suspiciously cheap SD cards on Amazon or AliExpress and most of those listings are scams. Their capacity and/or performance claims are lies, and they lure their victims by offering lower prices. Low price is a huge red flag but even adequately priced SD cards and flash drives might be fake or dysfunctional, and it&rsquo;s always a good idea to check them before you start to rely on them for storing your data. Luckily for us, it&rsquo;s very easy to test any SD card right from the Linux terminal.</p> <h2 id="fraud-is-everywhere">Fraud is Everywhere</h2> <p>Fake flash drives are still plentiful and major online retailers such as Amazon shamelessly advertise them on their websites:</p> <p><figure> <a href="https://bubelov.com/blog/2019/fake-flash/fake-sd-card_hu_62a052e2d8a10e0d.webp"> <img src="https://bubelov.com/blog/2019/fake-flash/fake-sd-card_hu_5e28d3a52fb27edf.webp" alt="" /> </a> <figcaption>Fake SD card</figcaption> </figure></p> <p><figure> <a href="https://bubelov.com/blog/2019/fake-flash/fake-usb-drive_hu_96a3d44f5c44fc4.webp"> <img src="https://bubelov.com/blog/2019/fake-flash/fake-usb-drive_hu_54fab0781ecfa581.webp" alt="" /> </a> <figcaption>Fake USB flash drive</figcaption> </figure></p> <h2 id="fight-flash-fraud">Fight Flash Fraud</h2> <p>Fight Flash Fraud (F3) is a popular open source set of utilities that can check any flash drive. It&rsquo;s <a href="http://oss.digirati.com.br/f3/">author</a> was inspired by a <a href="https://fightflashfraud.wordpress.com/">blog</a> named Fight Flash Fraud so I guess that&rsquo;s where this name came from.</p> <h2 id="installing-f3">Installing F3</h2> <p>F3 is easy to install on most of the Linux distributions but the exact installation instructions can be different. It&rsquo;s better to check the official package repos that come with your particular distro. I use Debian, so <a href="https://packages.debian.org">https://packages.debian.org</a> is a good first place to check for this tool. <a href="https://packages.debian.org/buster/f3">Here it is</a>. Alternatively, it can be found with the use of <code>apt</code> command line tool. Now that we know the package name, it&rsquo;s time to install it:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">sudo apt install f3</span></span></code></pre></div> <h2 id="using-f3">Using F3</h2> <p>The most powerful utility of the F3 pack is <code>f3probe</code>. It allows us to check the flash drives for almost every possible kind of fraud and it can also measure sequencial read and write speed. Here is how <code>f3probe</code> can be used to perform all of the basic tests:</p> <blockquote> <p>Make sure that the data on your flash drive is backed up because some of the tests might destroy any data remaining on your flash drive.</p> </blockquote> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">sudo f3probe /dev/mmcblk0</span></span></code></pre></div> <p>Please note that <code>/dev/mmcblk0</code> is the identifier of your device that is assigned by Linux when you connect it to your system. In my case, it&rsquo;s an SD card inserted in an SD card reader and such devices have a prefix <code>mmcblk</code>. Other devices, such as pen drives, usually have a prefix <code>sd</code>. You should locate your device first and use its location as an input to <code>f3probe</code> (the easiest way to do that is to run <code>lsblk</code> before and after you insert your flash drive, which allows you to quickly find the location of your flash drive).</p> <p>Hopefully, you should see a message like this:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Good news: The device `/dev/mmcblk0&#39; is the real thing</span></span></code></pre></div> <p>Another nice feature of <code>f3</code> suite is its ability to measure read and write speeds under load. In order to do that in a clean way, it&rsquo;s better to partition the SD card first:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">sudo fdisk /dev/mmcblk0</span></span></code></pre></div> <p>The <code>fdisk</code> utility will ask for your input. If your card has no partitions, just enter <code>n</code> (which means &ldquo;new partition&rdquo;), then you may choose to agree with the defaults and, finally, enter <code>w</code> command in order to save changes. If you already have a partition of the size of the whole card, you can keep it and ignore this command, or you can press <code>d</code> to delete that partition and then create the new one from scratch.</p> <p>Now, it&rsquo;s time to create a file system for our new partition:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">sudo mkfs.ext4 /dev/mmcblk0p1</span></span></code></pre></div> <p>Finally, we can mount the new partition we just created to the <code>/mount/sd/</code> directory</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">sudo mkdir -p /mnt/sd </span></span><span class="line"><span class="cl">sudo mount /dev/mmcblk0p1 /mnt/sd</span></span></code></pre></div> <p>Now we&rsquo;re ready to do some benchmarking. Let&rsquo;s start with measuring writing speed:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">sudo f3write /mnt/sd/</span></span></code></pre></div> <p>Here is an example output:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Average writing speed: 34.66 MB/s</span></span></code></pre></div> <p>Now that we have a bunch of files written on SD card, we can ask <code>f3read</code> to read them all and measure the average reading speed:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">sudo f3read /mnt/sd/</span></span></code></pre></div> <p>It should output something like this:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Average reading speed: 66.14 MB/s</span></span></code></pre></div> <h2 id="testing-different-sd-cards">Testing different SD cards</h2> <p>I tested a few of my SD cards and here is what I found:</p> <h3 id="card-1-sandisk-ultra-c10-16-gb-2012">Card 1: SanDisk Ultra C10 16 GB (2012)</h3> <ul> <li>Genuine</li> <li>Read: 39.72 MB/s</li> <li>Write: 5.91 MB/s</li> </ul> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">cd /sys/block/mmcblk0/device/ \ </span></span><span class="line"><span class="cl"> &amp;&amp; echo &#34;Device Manufacturer: $(cat manfid)&#34; \ </span></span><span class="line"><span class="cl"> &amp;&amp; echo &#34;Device Name: $(cat name)&#34; \ </span></span><span class="line"><span class="cl"> &amp;&amp; echo &#34;Hardware Revision: $(cat hwrev)&#34; \ </span></span><span class="line"><span class="cl"> &amp;&amp; echo &#34;Firmware Revision: $(cat fwrev)&#34; \ </span></span><span class="line"><span class="cl"> &amp;&amp; echo &#34;Manufacture Date: $(cat date)&#34;</span></span></code></pre></div> <ul> <li>Device Manufacturer: 0x000003</li> <li>Device Name: SU16G</li> <li>Hardware Revision: 0x8</li> <li>Firmware Revision: 0x0</li> <li>Manufacture Date: 12/2012</li> </ul> <p>This card has <a href="https://www.sdcard.org/developers/overview/speed_class/index.html">C10 speed class</a> which means that it should be capable to write data at the speed of at least 10 MB/s and, as we can see, it fails to do that. That&rsquo;s probably my fault because this card was exposed to water a few times and some of its connectors look damaged and rusty.</p> <h3 id="card-2-sandisk-ultra-c10-16-gb-2015">Card 2: SanDisk Ultra C10 16 GB (2015)</h3> <ul> <li>Genuine</li> <li>Read: 40.15 MB/s</li> <li>Write: 11.65 MB/s</li> <li>Device Manufacturer: 0x000003</li> <li>Device Name: SL16G</li> <li>Hardware Revision: 0x8</li> <li>Firmware Revision: 0x0</li> <li>Manufacture Date: 03/2015</li> </ul> <p>This card has <a href="https://www.sdcard.org/developers/overview/speed_class/index.html">C10 speed class</a> which means 10 MB/s, and it delivers on its promise.</p> <h3 id="card-3-samsung-evo-plus-u3-128-gb-2017">Card 3: Samsung EVO Plus U3 128 GB (2017)</h3> <ul> <li>Genuine</li> <li>Read: 66.14 MB/s</li> <li>Write: 34.66 MB/s</li> <li>Device Manufacturer: 0x00001b</li> <li>Device Name: ED4QT</li> <li>Hardware Revision: 0x3</li> <li>Firmware Revision: 0x0</li> <li>Manufacture Date: 07/2017</li> </ul> <p>This card has <a href="https://www.sdcard.org/developers/overview/speed_class/index.html">U3 speed class</a> which means that it should be capable to write data at the speed of at least 30 MB/s, which it does.</p> <h3 id="card-4-sandisk-edge-c10-a1-16-gb-raspberry-pi-4-desktop-kit-default-2019">Card 4: SanDisk EDGE C10 A1 16 GB (Raspberry Pi 4 Desktop Kit Default, 2019)</h3> <ul> <li>Genuine</li> <li>Read: 71.80 MB/s</li> <li>Write: 16.83 MB/s</li> </ul> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">cd /mnt/sd </span></span><span class="line"><span class="cl">sudo fio \ </span></span><span class="line"><span class="cl"> --name=test \ </span></span><span class="line"><span class="cl"> --filename=io-test-file \ </span></span><span class="line"><span class="cl"> --size=4G \ </span></span><span class="line"><span class="cl"> --ioengine=libaio \ </span></span><span class="line"><span class="cl"> --direct=1 \ </span></span><span class="line"><span class="cl"> --readwrite=randrw \ </span></span><span class="line"><span class="cl"> --rwmixread=75</span></span></code></pre></div> <ul> <li>Random Read: 2064kB/s, 503 IOPS</li> <li>Random Write: 690kB/s, 168 IOPS</li> <li>Device Manufacturer: 0x000003</li> <li>Device Name: SC16G</li> <li>Hardware Revision: 0x8</li> <li>Firmware Revision: 0x0</li> <li>Manufacture Date: 05/2019</li> </ul> <h3 id="card-5-sandisk-ultra-c10-a1-64-gb-2019">Card 5: SanDisk Ultra C10 A1 64 GB (2019)</h3> <ul> <li>Genuine</li> <li>Read: 75.63 MB/s</li> <li>Write: 31.92 MB/s</li> <li>Random Read: 1831kB/s, 447 IOPS</li> <li>Random Write: 612kB/s, 149 IOPS</li> <li>Device Manufacturer: 0x000003</li> <li>Device Name: SC64G</li> <li>Hardware Revision: 0x8</li> <li>Firmware Revision: 0x0</li> <li>Manufacture Date: 09/2019</li> </ul> <p>Interestingly, this card scored above V30 minimal requirement, but it is marked as C10 (3 times slower). I have no idea why SanDisk doesn&rsquo;t mark those cards as being able to write data at 30+ MB/s.</p> <h2 id="conclusion">Conclusion</h2> <p>It&rsquo;s always a good idea to test your flash drives before using them, especially if you just bought a new flash drive, and you have a limited time to return it in case it fails to deliver on its promises. F3 is a great set of tools aimed to detect fraud, and it also makes some basic performance metrics.</p> <p>Please note that it shows sequential read and write speed and not a random read/write speed, which is fine for the most of the use cases such as storing relatively large files or using your flash drive with a digital camera. Random speeds are more important for the tasks like running an operating system from your SD card. Some boards such as Raspberry Pi do exactly that, so you should also measure random read/write speed in order to find the best SD card for your board.</p> Europa Universalis IV https://bubelov.com/blog/2019/eu4/ Thu, 07 Nov 2019 00:00:00 +0000 https://bubelov.com/blog/2019/eu4/ <p><strong>WARNING: this game is extremely addictive!</strong></p> <p>I keep coming back to Europa Universalis IV at the end of every year. It&rsquo;s probably the best grand strategy game ever made, and it&rsquo;s so absorbing I often have to set alarms to remind myself to go to bed before 02:00, a lesson I learned the hard way after it wrecked my sleep schedule more than once.</p> <p>The game starts in 1444 and simulates about four centuries of history. It&rsquo;s open‑ended, with no single victory condition apart from, well, survival. To keep you busy, there&rsquo;s a detailed mission &ldquo;tree&rdquo; for each nation, largely based on its historical path.</p> <p>Europa Universalis IV isn&rsquo;t just about Europe. You can play as Asian powers, African kingdoms, and even Native American tribes, though I wouldn&rsquo;t recommend the latter for beginners. Dealing with those nasty colonists is a brutal challenge.</p> <p>Last time I played as Spain. Right now, I&rsquo;m playing as England, which has been especially engaging with the English-focused DLC, Rule Britannia.</p> Switching to LineageOS https://bubelov.com/blog/2019/switching-to-lineage-os/ Wed, 30 Oct 2019 00:00:00 +0000 https://bubelov.com/blog/2019/switching-to-lineage-os/ <p>Mobile operating systems are a mess. Basically, there are 2 most common options when it comes to buying a smartphone:</p> <ul> <li>Buying an expensive smartphone from Apple which has a closed source non-customizable operating system and a walled garden app store. Yes, you can&rsquo;t even install apps without a permission from Apple.</li> <li>Buying a reasonably priced smartphone with a clean open source OS, but bloated with terrible closed source adware planted by Google. It&rsquo;s so deeply integrated in most of Android distributions that you won&rsquo;t be able to get rid of it.</li> </ul> <p>I&rsquo;ve been in both camps for a while, and they&rsquo;re both suck. I&rsquo;ve heard about alternative options, but they all seemed like too much of a hassle. I have to admit, I&rsquo;ve been wrong: it&rsquo;s very easy to install an open source Android distribution such as <a href="https://lineageos.org/">LineageOS</a> on any of the <a href="https://wiki.lineageos.org/devices/">supported devices</a>, and it works like a charm. Google Services are not requited to run most of the apps, even if they state othervise. It&rsquo;s even possible to continue using your apps from Google Play with the help of <a href="https://f-droid.org/en/packages/com.aurora.store/">Aurora Store</a> and other projects aimed at freeing up people from Google&rsquo;s nasty grip.</p> GL.iNet GL-AR750S-Ext https://bubelov.com/blog/2019/gl-ar-750/ Sun, 27 Oct 2019 00:00:00 +0000 https://bubelov.com/blog/2019/gl-ar-750/ <p>I wanted to experiment with <a href="https://openwrt.org/">OpenWRT</a>, so I bought a GL.iNet travel router, mostly out of laziness, since they ship with OpenWRT pre-installed. The choice exceeded my expectations, and I&rsquo;ve had a lot of fun diving into the configuration.</p> <p>Having full SSH access to your router is a game-changer for control, though it also comes with the expected slick web interface. But the feature that amused me the most is a <strong>dedicated hardware switch</strong> on the device itself. Flick it, and it instantly wraps my entire wireless network in a WireGuard VPN tunnel. No software toggles, no app launches, just a physical button for full-network privacy. That&rsquo;s the kind of thoughtful, practical design I didn&rsquo;t know I wanted until I had it.</p> Nextcloud https://bubelov.com/blog/2019/nextcloud/ Fri, 25 Oct 2019 00:00:00 +0000 https://bubelov.com/blog/2019/nextcloud/ <p>I&rsquo;ve tried Nextcloud before and was disappointed with how it handles syncing large volumes of files, like photo galleries. I wouldn&rsquo;t use it for that again, at least not in its current state. But Nextcloud does much more than file sync.</p> <p>It comes with a suite of official, one‑click apps, and I was particularly drawn to &ldquo;Contacts&rdquo; and &ldquo;Calendar&rdquo;. That&rsquo;s exactly what I&rsquo;ve needed for a long time: a reliable, self‑hosted alternative to Google services that can keep my Debian laptop and LineageOS smartphone in sync.</p> RSS is Live and Kicking https://bubelov.com/blog/2019/rss-is-live-and-kicking/ Mon, 21 Oct 2019 00:00:00 +0000 https://bubelov.com/blog/2019/rss-is-live-and-kicking/ <p>I&rsquo;ve been experimenting with using Nextcloud as my RSS aggregator for the last couple of weeks. It works, but I wouldn&rsquo;t call it the best option.</p> <p>The main reason I&rsquo;m hesitant is the lack of visible development activity in the backend code and the official Android app. When a core feature feels stagnant, it&rsquo;s hard to trust its long-term future.</p> <p>There is a notable upside, though: the built‑in RSS server is compatible with third‑party clients like the popular <a href="https://jangernert.github.io/FeedReader/">FeedReeder</a> for Linux. This interoperability is a solid feature that keeps it usable even if the native apps lag behind.</p> <p>My trial is ongoing. If Nextcloud doesn&rsquo;t feel solid by the end of it, my fallback plan is to set up a dedicated <a href="https://tt-rss.org/">Tiny Tiny RSS</a> server, which has a reputation for being robust and actively maintained.</p> The Dark Side of the Raspberry Pi https://bubelov.com/blog/2019/dark-side-of-raspberry-pi/ Thu, 10 Oct 2019 00:00:00 +0000 https://bubelov.com/blog/2019/dark-side-of-raspberry-pi/ <p>I like my new Raspberry Pi 4, but let&rsquo;s be honest, it has some fundamental flaws. Contrary to popular belief, it&rsquo;s not primarily a Linux board. The real, low-level operating system running on every Raspberry Pi is <strong>ThreadX</strong>, a proprietary, closed-source real-time kernel. Popular Linux distributions like Raspbian are, architecturally speaking, <a href="https://ownyourbits.com/2019/02/02/whats-wrong-with-the-raspberry-pi/">second-class citizens</a> that run on top of this hidden firmware layer.</p> <p>This core firmware is closed source and stored on an EEPROM chip, which can be updated with a utility called <code>rpi-eeprom</code>. For some reason, my Raspbian installation never prompted me for a firmware update, which is a real shame. These updates often deliver important performance improvements and bug fixes, leaving them in the manual-update shadow is a user-experience misstep.</p> <p>And, to top it all off, this board still doesn&rsquo;t support <strong>USB boot</strong> out-of-the-box for the mass storage device class, a standard feature on many competing boards. For a device celebrated for its hackability and education mission, these closed, opaque elements and missing conveniences are really annoying.</p> Financial Privacy https://bubelov.com/blog/2019/privacy-payments/ Tue, 24 Sep 2019 00:00:00 +0000 https://bubelov.com/blog/2019/privacy-payments/ <p>The world is going cashless, and I don&rsquo;t think that anything can reverse this trend. There are plenty of reasons why people prefer cashless transactions and the most obvious one is convenience. Some older people still struggle to figure out how to make payments in a progressively cashless world but the younger generation reveals its overwhelming preference for cashless ways of paying for goods and services. I&rsquo;m not even going to list all the benefits of going cashless because they are obvious. Instead, I&rsquo;m going to cover the dark side of a cashless world.</p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#location-matters">Location Matters</a></li> <li><a href="#money-and-power">Money and Power</a></li> <li><a href="#surveillance-capitalism">Surveillance Capitalism</a></li> <li><a href="#why-cash-is-important">Why Cash is Important</a></li> <li><a href="#best-of-two-worlds">Best of Two Worlds?</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="location-matters">Location Matters</h2> <p>The popularity of cash payments varies from country to country, and it&rsquo;s not a question of technological advancement. USA is the catalyst of tech innovation but the staggering 40% of its payments are done in cash. On the contrary, Norway is far from being a tech hub filled with innovation, but it&rsquo;s citizens will soon forget what the Euro bill looks like: more than 90% of their payments are now cashless.</p> <p>What can explain such a difference in cash preference? No one knows for sure, but I tend to agree with people who say that its mostly a cultural thing. US culture is based on ideas of liberty, personal autonomy and decentralization of power. That would also explain the American obsession with stockpiling assault rifles. You know, just in case. Many Americans are less trusting towards their government and having control over your own money seems like a good way to protect an individual from administrative abuses.</p> <p>People in Scandinavian countries are more trusting to their governments and their peers so that might partially explain why they don&rsquo;t want to bear the costs of using cash.</p> <h2 id="money-and-power">Money and Power</h2> <p>Let&rsquo;s say you&rsquo;re a nasty bureaucrat, and you want to punish a journalist who tries to expose your hidden business empire. Killing a person would cross your moral red line, you might be a crook but not a murderer. What can you do to silence that inconvenient voice? What about calling your police friends and asking them to freeze the journalist&rsquo;s bank account. That would do the trick. Imagine how exposed the journalists might be in a progressively cashless society where it&rsquo;s hard to get or spend cash and where every cashless transaction can be blocked by your bank or your government. Bureaucrats and CEOs would become your masters and great power leads to great abuse.</p> <h2 id="surveillance-capitalism">Surveillance Capitalism</h2> <p>Do you eat enough healthy food? Are you a smoker? How much alcohol do you consume? Well, it&rsquo;s not my business, and you may not even know that yourself. I tried to record my eating habits for one month and, lets say the results did not match my expectations. Do you know who knows all of those answers for sure? It&rsquo;s your bank and your payment processor such as Visa and Mastercard. They don&rsquo;t only transfer money, they dig into your receipts and try to understand your spending habits and your lifestyle in general.</p> <p>What&rsquo;s wrong with that? Well, some people don&rsquo;t mind living under surveillance but let&rsquo;s think on what might motivate a company to collect such data. Indeed, there is only one true motivation for a company: profit. There are people who would love to know your spending habits down to a single transaction. This may sound far-fetched, but if you stop and think deeply about this, all pieces of this puzzle start to click. Next time you get an ever-increasing bill for medical insurance, it might be because of that pack of cigarettes you paid for because one of your friends forgot to take their wallet.</p> <h2 id="why-cash-is-important">Why Cash is Important</h2> <blockquote> <p>Money is coined liberty, and so it is ten times dearer to a man who is deprived of freedom. If money is jingling in his pocket, he is half consoled, even though he cannot spend it. ― <a href="https://en.wikipedia.org/wiki/The_House_of_the_Dead_(novel)">The House of the Dead</a></p> </blockquote> <p>Cash is not perfect, and it&rsquo;s expected to become less convenient in the future. I believe that there is nothing that can stop its demise and that should be worrying. The cash system spreads the spending power of individuals more or less evenly, and it might be argued that having free and uninterruptible transactions are more important than many cornerstones of our society such as the democracy itself. People tend cast one vote every few years, but they may cast dozens of votes every day by preferring one product over another. When you vote for your representative, you have to choose between a few clowns all of whom might not (and often, do not) deserve your trust. When you buy something, you often know exactly what you&rsquo;re going to get, and you can sue a seller if he over-promised and under-delivered. Our well-being depends on uninterrupted ability to express our preferences and nothing good can come from having a middleman who can potentially disturb that constant flow of &ldquo;votes&rdquo;.</p> <p>Speaking of disruptors: have you noticed that the interest rates on bank deposits are close to zero, and we don&rsquo;t even include inflation. That is a harsh treatment of anyone who dares to try to save money in a world where central bankers want everyone to go on a debt fueled spending frenzy. Why those interest rates can&rsquo;t go below zero? The only thing that stops the world from punishing savers even more is cash. If a government decides to force its banks to offer negative interest rates on bank deposits, we would still have the option to withdraw our money in cash and avoid bearing ever-increasing costs of someone&rsquo;s reckless policies.</p> <p>To have no cash is to have no exit and that would be pretty scary. Fortunately for us, most central banks can&rsquo;t ban cash and recent surveys suggest that people are strongly opposed to going fully cashless.</p> <h2 id="best-of-two-worlds">Best of Two Worlds?</h2> <p>That would be really nice to combine the convenience of cashless payments and the privacy and self-sovereignty of cash. We&rsquo;re not there yet, but I think that Bitcoin can solve that problem. Unfortunately, it&rsquo;s not that convenient now, and it&rsquo;s also quite volatile which blocks its adoption as a medium of exchange. It can serve as a promising asset class for many investors, and it&rsquo;s often used as a store of value in countries going through hyperinflation, but I think that Bitcoin&rsquo;s use cases will expand in the future.</p> <h2 id="conclusion">Conclusion</h2> <p>The world is going cashless, and that creates a lot of risks for consumers. I compare it to smoking: it feels good in the moment, but does a lot of harm in the long run. It&rsquo;s easy to get hooked on the short‑term perks and forget the downstream dangers.</p> <p>I sometimes watch old movies where everyone is a smoker, people even lighting up on flights. It looks hilariously stupid now, and it makes me wonder what else we&rsquo;ll see as stupid and harmful a few decades from now. Back then, the whole world seemed built for smokers, which of course encouraged more people to smoke by making it convenient.</p> <p>Those days are gone. Now we try to do the opposite: make harmful long‑term behaviors more and more inconvenient. I&rsquo;m pretty sure exposing all our financial data will be one of those things we look back on and wince at.</p> Human Action https://bubelov.com/blog/2019/human-action/ Thu, 12 Sep 2019 00:00:00 +0000 https://bubelov.com/blog/2019/human-action/ <p>I don&rsquo;t usually keep more than two or three quotes when I read a good book. Some books offer new knowledge and others try to find new ways of explaining old and complex knowledge using simple words. In both cases, I really appreciate a book that can provide a few deep thoughts or to explain a complex thing in a way that makes it easier to understand. After I finished Human Action, I ended up with about 150 new quotes and notes. This book is amazing, to say the least!</p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#core-ideas">Core Ideas</a></li> <li><a href="#rejection-of-intrinsic-value">Rejection of Intrinsic Value</a></li> <li><a href="#entrepreneurs-playing-prophet">Entrepreneurs: Playing Prophet</a></li> <li><a href="#inflation">Inflation</a></li> <li><a href="#debt">Debt</a></li> <li><a href="#taxes">Taxes</a></li> <li><a href="#conclusion">Conclusion</a></li> <li><a href="#appendix-some-interesting-quotes">Appendix: Some Interesting Quotes</a></li> </ul> </nav> </div> <h2 id="core-ideas">Core Ideas</h2> <p>The basic idea of this book is pretty simple: <em>humans act</em>. This is an obvious observation that may seem unimportant but Ludwig von Mises uses this insight as a core of his powerful theory that gives us a better understanding of economics. Our life is filled with economic calculation; by understanding economics we&rsquo;re at a better position to understand the life itself. Reading Human Action is an interesting journey which starts from a single trivial observation.</p> <p>Another core idea of Human Action is that economics is a social science and that the relations between things on a market are not fixed. That makes it inherently hard or even futile to infer any knowledge from the data alone. That&rsquo;s also a big problem for the people who want to &ldquo;conquer&rdquo; economics in the same way as a natural science. For instance, physics benefits from the fact that the relationships between many things in its domain are constant and predictable; that&rsquo;s not the case for economics.</p> <h2 id="rejection-of-intrinsic-value">Rejection of Intrinsic Value</h2> <p>The author fully rejects the idea that anything has an objective value so the value can&rsquo;t be separated from subjective evaluations of each individual. Mises concludes that we value things according to their ability to satisfy our most urgent needs and that&rsquo;s the only thing that matters. That sounds fair for consumers&rsquo; goods but what about raw materials? Their price can be inferred from the price of consumers&rsquo; goods as they are necessary to produce them. That lets the market to balance itself, it doesn&rsquo;t allow us to waste raw materials on things that are of lesser importance to consumers.</p> <h2 id="entrepreneurs-playing-prophet">Entrepreneurs: Playing Prophet</h2> <p>Mises argues that we owe the benefits of progress to our entrepreneurial activity: actions of entrepreneurs. He gives a rather broad definition of entrepreneurship which covers almost any of man&rsquo;s actions. We&rsquo;re all entrepreneurs in a sense that we try to anticipate the future and improve our conditions. People who better anticipate the future tend to reap profits and people who place wrong bets are doomed to suffer losses:</p> <blockquote> <p>The ultimate source of profits is always the foresight of future conditions. Those who succeeded better than others in anticipating future events and in adjusting their activities to the future state of the market, reap profits because they are in a position to satisfy the most urgent needs of the public.</p> </blockquote> <h2 id="inflation">Inflation</h2> <p>Human Action contains a lot of thoughts on inflation. This book puts an emphasis on the fact that we simply can&rsquo;t print our way to prosperity and the fact that inflation is always discriminatory. It reminded me of the famous mental experiment which shows that counterfeiting money is not as bad as it seems: inflation is more tricky than it might look from the first glance.</p> <p>Let&rsquo;s say John lives in a small town in Spain and he found a way to print one billion Euros unnoticed. It might be tempting to argue that it&rsquo;s bad because it causes inflation so John would force the society to pay the bill for his luxurious lifestyle in the form of inflation. How much inflation can one billion Euros cause? It would be barely noticeable and the people from John&rsquo;s city would actually be better off because they can sell more goods and services to John and have their share of newly created money.</p> <p>Is inflation good then? It&rsquo;s good for John and his community, that&rsquo;s for sure. Probably it&rsquo;s also good for some neighboring towns his community has trade with and it can even be beneficial for the whole of Spain. That&rsquo;s one of the most important things about inflation, it&rsquo;s inherently discriminatory. It can be great for people who get new money first but everyone else ends up footing the bill.</p> <p>Therefore, Mises concludes that inflation can&rsquo;t be a good instrument if your goal is to make our society more prosperous but it can certainly make privileged elites more wealthy at the expense of everyone else.</p> <h2 id="debt">Debt</h2> <p>Ludwig von Mises hates government bonds, that&rsquo;s for sure:</p> <blockquote> <p>It (government bond) opened a way to free the individual from the necessity of risking and acquiring his wealth and his income anew each day in the capitalist market. He who invested his funds in bonds issued by the government and its subdivisions was no longer subject to the inescapable laws of the market and to the sovereignty of the consumers.</p> </blockquote> <p>Is this negative attitude justified? It very well may be, such bonds are often called &ldquo;risk free&rdquo; but it looks like that it&rsquo;s just a way of profiteering from the coercive power of a state to extract money from its people in order to pay back the debts.</p> <p>Here is another interesting observation regarding government debt:</p> <blockquote> <p>The trumpery argument that the public debt is no burden because “we owe it to ourselves” is delusive. The Pauls of 1940 do not owe it to themselves. It is the Peters of 1970 who owe it to the Pauls of 1940. The whole system is the acme of the short-run principle. The statesmen of 1940 solve their problems by shifting them to the statesmen of 1970. On that date the statesmen of 1940 will be either dead or elder statesmen glorying in their wonderful achievement, social security.</p> </blockquote> <p>It&rsquo;s hard to disagree with that too: someone has to pay back the debts that our governments take today. Our over spending tends to be shifted to future generations and that will make their life harder.</p> <h2 id="taxes">Taxes</h2> <p>The author is not an anarchist, and he leaves some space for state and taxes, but he takes an issue with how those taxes are usually collected:</p> <blockquote> <p>Taxes are necessary. But the system of discriminatory taxation universally accepted under the misleading name of progressive taxation of income and inheritance is not a mode of taxation. It is rather a mode of disguised expropriation of the successful capitalists and entrepreneurs.</p> </blockquote> <p>This is an unpopular opinion, who has any pity for billionaires? In my view, progressive taxation is morally wrong, but it&rsquo;s worth remembering that it&rsquo;s also counterproductive. Mises abstains from the moral arguments in this book. Instead, he attacks progressive taxation from a strictly utilitarian perspective.</p> <h2 id="conclusion">Conclusion</h2> <p>Human Action is a masterpiece, and I think that it&rsquo;s the book that everyone should read. Economics is not that hard and it can also be fun and interesting. Unfortunately, modern economics may look intimidating with all of those formulas and complex models which assume that people are perfectly rational. Its worth remembering that this &ldquo;data driven&rdquo; and &ldquo;rational&rdquo; orthodoxy is not universally accepted and there are competing and more intuitive ways to understand what&rsquo;s going on and prepare for the future. This book is a great example of a simple and intuitive approach to understanding economics and that&rsquo;s one of the reasons why this book can be a good read for everyone regardless of their prior knowledge of economics.</p> <h2 id="appendix-some-interesting-quotes">Appendix: Some Interesting Quotes</h2> <blockquote> <p>It was asserted that a rise or fall in the quantity of money in circulation must result in proportional changes of commodity prices. Modern economics has clearly and irrefutably exposed the fallaciousness of this statement.</p> </blockquote> <blockquote> <p>It is a fact that in the armed conflicts fought in the past between Europeans and backward peoples of other races, one European soldier was usually a match for several native fighters.</p> </blockquote> <blockquote> <p>Value is not intrinsic, it is not in things. It is within us; it is the way in which man reacts to the conditions of his environment.</p> </blockquote> <blockquote> <p>It is generally believed by those unfamiliar with economic theory that credit expansion and an increase in the quantity of money in circulation are efficacious means for lowering the rate of interest permanently below the height it would attain on a nonmanipulated capital and loan market.</p> </blockquote> <blockquote> <p>No other distinction is of greater significance, both for human life and for the study of human action, than that between calculable action and noncalculable action.</p> </blockquote> <blockquote> <p>The entrepreneur, grown old and weary and no longer prepared to risk his hard-earned wealth by new attempts to meet the wants of consumers, and the heir of other people&rsquo;s profits, lazy and fully conscious of his own inefficiency, preferred investment in bonds of the public debt because they wanted to be free from the law of the market.</p> </blockquote> <blockquote> <p>The ultimate source from which entrepreneurial profit and loss are derived is the uncertainty of the future constellation of demand and supply.</p> </blockquote> <blockquote> <p>There is no security and no such thing as a right to preserve any position acquired in the past. Nobody is exempt from the law of the market, the consumers&rsquo; sovereignty.</p> </blockquote> <blockquote> <p>Against such statements it is necessary to emphasize that, so far as the operation of the market is not sabotaged by the interference of governments and other factors of coercion, success in business is the proof of services rendered to the consumers.</p> </blockquote> <blockquote> <p>It is a widespread fallacy that skillful advertising can talk the consumers into buying everything that the advertiser wants them to buy. The consumer is, according to this legend, simply defenseless against “high-pressure” advertising. If this were true, success or failure in business would depend on the mode of advertising only. However, nobody believes that any kind of advertising would have succeeded in making the candlemakers hold the field against the electric bulb, the horsedrivers against the motorcars, the goose quill against the steel pen and later against the fountain pen.</p> </blockquote> <blockquote> <p>Each individual, in buying or not buying and in selling or not selling, contributes his share to the formation of the market prices.</p> </blockquote> <blockquote> <p>Their offers are limited on the one hand by their anticipation of future prices of the products and on the other hand by the necessity to snatch the factors of production away from the hands of other entrepreneurs competing with them.</p> </blockquote> <blockquote> <p>It (competition) reflects in the external world the conflict which the inexorable scarcity of the factors of production brings about in the soul of each individual. It makes effective the subsumed decisions of the consumers as to what purpose the nonspecific factors should be used for and to what extent the specific factors of production should be used.</p> </blockquote> <blockquote> <p>First, valuing that results in action always means preferring and setting aside; it never means equivalence. Second, there is no means of comparing the valuations of different individuals or the valuations of the same individuals at different instants other than by establishing whether or not they arrange the alternatives in question in the same order of preference.</p> </blockquote> <blockquote> <p>The mere existence of monopoly does not mean anything. The publisher of a copyright book is a monopolist. But he may not be able to sell a single copy, no matter how low the price he asks. Not every price at which a monopolist sells a monopolized commodity is a monopoly price.</p> </blockquote> <blockquote> <p>Even if this were true, it would still be faulty to explain the purchasing power—the price— of the monetary unit on the basis of its services. The services rendered by water, whisky, and coffee do not explain the prices paid for these things. What they explain is only why people, as far as they recognize these services, under certain further conditions demand definite quantities of these things. It is always demand that influences the price structure, not the objective value in use.</p> </blockquote> <blockquote> <p>It is demand, a subjective element whose intensity is entirely determined by value judgments, and not any objective fact, any power to bring about a certain effect, that plays a role in the formation of the market&rsquo;s exchange ratios.</p> </blockquote> <blockquote> <p>It is these differences in the marketability of the various commodities and services which created indirect exchange. A man who at the instant cannot acquire what he wants to get for the conduct of his own household or business, or who does not yet know what kind of goods he will need in the uncertain future, comes nearer to his ultimate goal if he exchanges a less marketable good he wants to trade against a more marketable one. It may also happen that the physical properties of the merchandise he wants to give away (as, for instance, its perishability or the costs incurred by its storage or similar circumstances) impel him not to wait longer. Sometimes he may be prompted to hurry in giving away the good concerned because he is afraid of a deterioration of its market value. In all such cases he improves his own situation in acquiring a more marketable good, even if this good is not suitable to satisfy directly any of his own needs.</p> </blockquote> <blockquote> <p>The insight that the exchange ratio between money on the one hand and the vendible commodities and services on the other is determined, in the same way as the mutual exchange ratios between the various vendible goods, by demand and supply was the essence of the quantity theory of money. This theory is essentially an application of the general theory of supply and demand to the special instance of money. Its merit was the endeavor to explain the determination of money&rsquo;s purchasing power by resorting to the same reasoning which is employed for the explanation of all other exchange ratios. Its shortcoming was that it resorted to a holistic interpretation. It looked at the total supply of money in the Volkswirtschaft and not at the actions of the individual men and firms. An outgrowth of this erroneous point of view was the idea that there prevails a proportionality in the changes of the—total—quantity of money and of money prices.</p> </blockquote> <blockquote> <p>As soon as an economic good is demanded not only by those who want to use it for consumption or production, but also by people who want to keep it as a medium of exchange and to give it away at need in a later act of exchange, the demand for it increases.</p> </blockquote> <blockquote> <p>No good can be employed for the function of a medium of exchange which at the very beginning of its use for this purpose did not have exchange value on account of other employments.</p> </blockquote> <blockquote> <p>The absence of action is not only the result of full satisfaction; it can no less be the corollary of the inability to render things more satisfactory. It can mean hopelessness as well as contentment.</p> </blockquote> <blockquote> <p>As money can never be neutral and stable in purchasing power, a government&rsquo;s plans concerning the determination of the quantity of money can never be impartial and fair to all members of society. Whatever a government does in the pursuit of aims to influence the height of purchasing power depends necessarily upon the rulers&rsquo; personal value judgments. It always furthers the interests of some groups of people at the expense of other groups. It never serves what is called the commonweal or the public welfare. In the field of monetary policies too there is no such thing as a scientific ought.</p> </blockquote> <blockquote> <p>For two hundred years the governments have interfered with the market&rsquo;s choice of the money medium. Even the most bigoted étatists do not venture to assert that this interference has proved beneficial.</p> </blockquote> <blockquote> <p>The course of a progressing inflation is this: At the beginning the inflow of additional money makes the prices of some commodities and services rise; other prices rise later. The price rise affects the various commodities and services, as has been shown, at different dates and to a different extent.</p> </blockquote> <blockquote> <p>This first stage of the inflationary process may last for many years. While it lasts, the prices of many goods and services are not yet adjusted to the altered money relation. There are still people in the country who have not yet become aware of the fact that they are confronted with a price revolution which will finally result in a considerable rise of all prices, although the extent of this rise will not be the same in the various commodities and services. These people still believe that prices one day will drop. Waiting for this day, they restrict their purchases and concomitantly increase their cash holdings. As long as such ideas are still held by public opinion, it is not yet too late for the government to abandon its inflationary policy. But then finally the masses wake up. They become suddenly awareof the fact that inflation is a deliberate policy and will go on endlessly. A breakdown occurs. The crack-up boom appears. Everybody is anxious to swap his money against “real” goods, no matter whether he needs them or not, no matter how much money he has to pay for them. Within a very short time, within a few weeks or even days, the things which were used as money are no longer used as media of exchange. They become scrap paper. Nobody wants to give away anything against them.</p> </blockquote> <blockquote> <p>Fiat money is a money consisting of mere tokens which can neither be employed for any industrial purposes nor convey a claim against anybody.</p> </blockquote> <blockquote> <p>First: Inflationary or expansionist policy must result in overconsumption on the one hand and in malinvestment on the other. It thus squanders capital and impairs the future state of want-satisfaction. Second: The inflationary process does not remove the necessity of adjusting production and reallocating resources. It merely postpones it and thereby makes it more troublesome. Third: Inflation cannot be employed as a permanent policy because it must, when continued, finally result in a breakdown of the monetary system.</p> </blockquote> <blockquote> <p>Today even the most bigoted étatists cannot deny that all the alleged evils of free banking count little when compared with the disastrous effects of the tremendous inflations which the privileged and government-controlled banks have brought about.</p> </blockquote> <blockquote> <p>Only free banking would have rendered the market economy secure against crises and depressions.</p> </blockquote> <blockquote> <p>They adopted the superstition that lowering the rate of interest is beneficial and that credit expansion is the right means of attaining such cheap money. Nothing harmed the cause of liberalism more than the almost regular return of feverish booms and of the dramatic breakdown of bull markets followed by lingering slumps. Public opinion has become convinced that such happenings are inevitable in the unhampered market economy. People did not conceive that what they lamented was the necessary outcome of policies directed toward a lowering of the rate of interest by means of credit expansion.</p> </blockquote> <blockquote> <p>Government interference with the present state of banking affairs could be justified if its aim were to liquidate the unsatisfactory conditions by preventing or at least seriously restricting any further credit expansion. In fact, the chief objective of present-day government interference is to intensify further credit expansion. This policy is doomed to failure. Sooner or later it must result in a catastrophe.</p> </blockquote> <blockquote> <p>The difference between the trade in money and that in the vendible commodities is this: As a rule commodities move on a one-way road, viz., from the places of surplus production to those of surplus consumption. Consequently the price of a certain commodity in the places of surplus production is as a rule lower by the amount of shipping costs than in the places of surplus consumption. Things are different with money if we do not take into account the conditions of the gold-mining countries and of those countries whose residents deliberately aim at altering the size of their cash holdings. Money moves now this way, now that. At one time a country exports money, at another time it imports money. Every exporting country very soon becomes an importing country precisely on account of its previous exports. For this reason alone it is possible to save the costs of shipping money by the interplay of the market for foreign exchange.</p> </blockquote> <blockquote> <p>The use of money does not remove the differences which exist between the various nonmonetary goods with regard to their marketability. In the money economy there is a very substantial difference between the marketability of money and that of the vendible goods. But there remain differences between the various specimens of this latter group. For some of them it is easier to find without delay a buyer ready to pay the highest price which, under the state of the market, can possibly be attained. With others it is more difficult. A first-class bond is more marketable than a house in a city&rsquo;s main street, and an old fur coat is more marketable than an autograph of an eighteenth-century statesman. One no longer compares the marketability of the various vendible goods with the perfect marketability of money. One merely compares the degree of marketability of the various commodities. One may speak of the secondary marketability of the vendible goods.</p> </blockquote> <blockquote> <p>The only cautious method of dealing with hot money would have been to keep a reserve of gold and foreign exchange big enough to pay back the whole amount in case of a sudden withdrawal. Of course, this method would have required the banks to charge the customers a commission for keeping their funds safe.</p> </blockquote> <blockquote> <p>The gold standard removes the determination of cash-induced changes in purchasing power from the political arena. Its general acceptance requires the acknowledgment of the truth that one cannot make all people richer by printing money. The abhorrence of the gold standard is inspired by the superstition that omnipotent governments can create wealth out of little scraps of paper.</p> </blockquote> <blockquote> <p>People fight the gold standard because they want to substitute national autarky for free trade, war for peace, totalitarian government omnipotence for liberty.</p> </blockquote> <blockquote> <p>Other things being equal, satisfaction in a nearer period of the future is preferred to satisfaction in a more distant period; disutility is seen in waiting.</p> </blockquote> <blockquote> <p>The theorem of time preference must be demonstrated in a double way. First for the case of plain saving in which people must choose between the immediate consumption of a quantity of goods and the later consumption of the same quantity. Second for the case of capitalist saving in which the choice is to be made between the immediate consumption of a quantity of goods and the later consumption either of a greater quantity or of goods which are fit to provide a satisfaction which—except for the difference in time—is valued more highly. The proof has been given for both cases. No other case is thinkable.</p> </blockquote> <blockquote> <p>Saving is the first step on the way toward improvement of material well-being and toward every further progress on this way.</p> </blockquote> <blockquote> <p>These nations of Eastern Europe, Asia, and Africa have been able, thanks to the foreign capital imported, to reap the fruits of modern industry at an earlier date. They were to some extent relieved from the necessity of restricting their consumption in order to accumulate a sufficient stock of capital goods. This was the true nature of the alleged exploitation of the backward nations on the part of western capitalism about which their nationalists and the Marxians lament.</p> </blockquote> <blockquote> <p>If action is primarily directed toward the improvement of other people&rsquo;s conditions and is therefore commonly called altruistic, the uneasiness the actor wants to remove is his own present dissatisfaction with the expected state of other people&rsquo;s affairs in various periods of the future. In taking care of other people he aims at alleviating his own dissatisfaction.</p> </blockquote> <blockquote> <p>War is the alternative to freedom of foreign investment as realized by the international capital market.</p> </blockquote> <blockquote> <p>It would be more realistic to blame capitalism for its propensity to over-value useless innovations than for its alleged suppression of useful innovations. It is a fact that large sums have been wasted for the purchase of quite useless patent rights and for fruitless ventures to apply them in practice.</p> </blockquote> <blockquote> <p>It is true, we would be still better off if our ancestors and we ourselves in our past actions had succeeded in better anticipating the conditions under which we must act today. The cognizance of this fact explains many phenomena of our time. But it does not cast any blame upon the past nor does it show any imperfection inherent in the market economy.</p> </blockquote> <blockquote> <p>The act of saving always has its counterpart in a supply of goods produced and not consumed, of goods available for further production activities. A man&rsquo;s savings are always embodied in concrete capital goods.</p> </blockquote> <blockquote> <p>If future goods were not bought and sold at a discount as against present goods, the buyer of land would have to pay a price which equals the sum of all future net revenues and which would leave nothing for a current reiterated income.</p> </blockquote> <blockquote> <p>People do not save and accumulate capital because there is interest. Interest is neither the impetus to saving nor the reward or the compensation granted for abstaining from immediate consumption. It is the ratio in the mutual valuation of present goods as against future goods.</p> </blockquote> <blockquote> <p>Calculating the prospects of the profitability of the investment, he comes to the conclusion that the expected proceeds are not great enough to cover the costs of material and labor to be expended and interest on the capital to be invested. He renounces the execution of project A and embarks instead upon the realization of another plan, B. According to plan B the hotel is to be erected in a more easily accessible location which does not offer all the advantages of the picturesque landscape which plan A had selected, but in which it can be built either with lower costs of construction or finished in a shorter time. If no interest on the capital invested were to enter into the calculation, the illusion could arise that the state of the market data—supply of capital goods and the valuations of the public—allows for the execution of plan A. However, the realization of plan A would withdraw scarce factors of production from employments in which they could satisfy wants considered more urgent by the consumers. It would mean a manifest malinvestment, a squandering of the means available.</p> </blockquote> <blockquote> <p>Therefore there cannot be any question of abolishing interest by any institutions, laws, and devices of bank manipulation. He who wants to “abolish” interest will have to induce people to value an apple available in a hundred years no less than a present apple. What can be abolished by laws and decrees is merely the right of the capitalists to receive interest. But such laws would bring about capital consumption and would very soon throw mankind back into the original state of natural poverty.</p> </blockquote> <blockquote> <p>The increase or decrease in the supply of money (in the broader sense) can increase or decrease the supply of money offered on the loan market and thereby lower or raise the gross market rate of interest although no change in the rate of original interest has taken place. If this happens, the market rate deviates from the height which the state of originary interest and the supply of capital goods available for production would require. Then the market rate of interest fails to fulfill the function it plays in guiding entrepreneurial decisions. It frustrates the entrepreneur&rsquo;s calculation and diverts his actions from those lines in which they would in the best possible way satisfy the most urgent needs of the consumers.</p> </blockquote> <blockquote> <p>The individual is always ready to ascribe his good luck to his own efficiency and to take it as a well-deserved reward for his talent, application, and probity. But reverses of fortune he always charges to other people, and most of all to the absurdity of social and political institutions. He does not blame the authorities for having fostered the boom. He reviles them for the necessary collapse.</p> </blockquote> <blockquote> <p>What distinguishes the successful entrepreneur and promoter from other people is precisely the fact that he does not let himself be guided by what was and is, but arranges his affairs on the ground of his opinion about the future.</p> </blockquote> <blockquote> <p>On the other hand a worker eager to earn more must either shift to piecework or seek a job in which pay is higher because the minimum of achievement expected is greater.</p> </blockquote> <blockquote> <p>Slavery and serfdom were abolished by political action dictated by the spirit of the much-abused laissez faire, laissez passer ideology.</p> </blockquote> <blockquote> <p>The produced factors of production perish sooner or later entirely in the pursuit of production processes, and piecemeal are transformed into consumers&rsquo; goods which are eventually consumed. If one does not want to make the results of past saving and capital accumulation disappear, one must, apart from consumers&rsquo; goods, also produce that amount of capital goods which is needed for the replacement of those worn out.</p> </blockquote> <blockquote> <p>It is vain to search for coefficients of correlation if one does not start from a theoretical insight acquired beforehand. The co efficient may have a high numerical value without indicating any significant and relevant connection between the two groups.</p> </blockquote> <blockquote> <p>What the government spends more, the public spends less. Public works are not accomplished by the miraculous power of a magic wand. They are paid for by funds taken away from the citizens. If the government had not interfered, the citizens would have employed them for the realization of profit promising projects the realization of which they must omit because their means have been curtailed by the government.</p> </blockquote> <blockquote> <p>The changes in the data whose reiterated emergence prevents the economic system from turning into an evenly rotating economy and produces again and again entrepreneurial profit and loss are favorable to some members of society and unfavorable to others. Hence, people concluded, the gain of one man is the damage of another; no man profits but by the loss of others. This dogma was already advanced by certain ancient authors. Among modern writers Montaigne was the first to restate it; we may fairly call it the Montaigne dogma. It was the quintessence of the doctrines of Mercantilism, old and new. It is at the bottom of all modern doctrines teaching that there prevails, within the frame of the market economy, an irreconcilable conflict among the interests of various social classes within a nation and furthermore between the interests of any nation and those of all other nations.</p> </blockquote> <blockquote> <p>What produces a man&rsquo;s profit in the course of affairs within an unhampered market society is not his fellow citizen&rsquo;s plight and distress, but the fact that he alleviates or entirely removes what causes his fellow citizen&rsquo;s feeling of uneasiness.</p> </blockquote> <blockquote> <p>The transition to capitalism—i.e., the removal of the obstacles which in former days had fettered the functioning of private initiative and enterprise—has consequently deeply influenced sexual customs. It is not the practice of birth control that is new, but merely the fact that it is more frequently resorted to.</p> </blockquote> <blockquote> <p>The transition to capitalism is thus accompanied by two phenomena: a decline both in fertility rates and in mortality rates. The average duration of life is prolonged.</p> </blockquote> <blockquote> <p>Socialism is not an alternative to capitalism; it is an alternative to any system under which men can live as human beings. To stress this point is the task of economics as it is the task of biology and chemistry to teach that potassium cyanide is not a nutriment but a deadly poison.</p> </blockquote> <blockquote> <p>What the naïve mind calls reason is nothing but the absolutization of its own value judgments. The individual simply identifies the products of his own reasoning with the shaky notion of an absolute reason. No socialist ever gave a thought to the possibility that the abstract entity which he wants to vest with unlimited power—whether it is called humanity, society, nation, state, or government—could act in a way of which he himself disapproves. A socialist advocates socialism because he is fully convinced that the supreme director of the socialist commonwealth will be reasonable from his—the individual socialist&rsquo;s —point of view, that he will aim at those ends of which he—the individual socialist—fully approves, and that he will try to attain these ends by choosing means which he—the individual socialist—would also choose.</p> </blockquote> <blockquote> <p>To the farmer no price of wheat, however high, appears unjust. To the wage earner no wage rates, however high, appear unfair. But the farmer is quick to denounce every drop in the price of wheat as a violation of divine and human laws, and the wage earners rise in rebellion when their wages drop.</p> </blockquote> <blockquote> <p>The alternative is not plan or no plan. The question is whose planning? Should each member of society plan for himself, or should a benevolent government alone plan for them all? The issue is not automatism versus conscious action; it is autonomous action of each individual versus the exclusive action of the government. It is freedom versus government omnipotence.</p> </blockquote> <blockquote> <p>The fundamental error of the interventionists consists in the fact that they ignore the shortage of capital goods. In their eyes the depression is merely caused by a mysterious lack of the people&rsquo;s propensity both to consume and to invest. While the only real problem is to produce more and to consume less in order to increase the stock of capital goods available, the interventionists want to increase both consumption and investment. They want the government to embark upon projects which are unprofitable precisely because the factors of production needed for their execution must be withdrawn from other lines of employment in which they would fulfill wants the satisfaction of which the consumers consider more urgent. They do not realize that such public works must considerably intensify the real evil, the shortage of capital goods.</p> </blockquote> <blockquote> <p>In the unhampered market the behavior of consumers, their buying or abstention from buying, ultimately determines each individual&rsquo;s income and wealth. Should one vest in the government the power to overrule the consumers&rsquo; choices?</p> </blockquote> <blockquote> <p>What is needed to make peace durable is to dethrone the despots. This, of course, cannot be achieved peacefully. It is necessary to crush the mercenaries of the kings. But this revolutionary war of the peoples against the tyrants will be the last war, the war to abolish war forever.</p> </blockquote> <blockquote> <p>The entrepreneurial idea that carries on and brings profit is precisely that idea which did not occur to the majority. It is not correct foresight as such that yields profits, but foresight better than that of the rest. The prize goes only to those dissenters who do not let themselves be misled by the errors accepted by the multitude. What makes profits emerge is the provision for future needs for which others have neglected to make adequate provision.</p> </blockquote> <blockquote> <p>Those ignorant people who operate on the stock and commodity exchanges according to tips are destined to lose their money, from whatever source they may have got their inspiration and “inside” information.</p> </blockquote> Email Privacy https://bubelov.com/blog/2019/email-privacy/ Thu, 29 Aug 2019 00:00:00 +0000 https://bubelov.com/blog/2019/email-privacy/ <p>I&rsquo;m happy to see that privacy has become a hot topic recently. More and more people want to know who holds their data and what kind of data is it. I don&rsquo;t think this trend will stop any time soon because most people don&rsquo;t seem to like the answers to all those questions when they find them. Many email providers use the data they hold to maximize their profits at their users&rsquo; expense.</p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#the-problem-with-email">The Problem With Email</a></li> <li><a href="#why-email-should-be-private">Why Email Should be Private?</a></li> <li><a href="#hard-solution-self-hosted-email-servers">Hard Solution: Self Hosted Email Servers</a></li> <li><a href="#easier-solution-email-services-with-zero-access-policies">Easier Solution: Email Services With Zero Access Policies</a></li> <li><a href="#we-need-a-better-protocol">We Need a Better Protocol</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="the-problem-with-email">The Problem With Email</h2> <p>It&rsquo;s hard to get some privacy in the world dominated by a bunch of tech companies making money by following your every step online and offline. It&rsquo;s not an exaggeration, Google has its trackers installed in more than 50% of popular web pages and that fact alone should make many people uncomfortable. Google and Apple also collect your location data via their smartphones. Nowadays, you can&rsquo;t own a smartphone, without it acting in the interests of a big corporation, not yours, even if you paid a lot of money for it.</p> <p>The situation with email services is rather bleak. Various data sources estimate that Google Gmail controls about 30 to 35 percent of the global email market, and they&rsquo;re known for constantly reading user emails and selling all the information they can find to the highest bidder.</p> <h2 id="why-email-should-be-private">Why Email Should be Private?</h2> <p>Making your email communications private is a good starting point for someone who wants to take back his or her data and make sure that no third party has access to it. Just a few decades ago, people didn&rsquo;t have email accounts, they used paper mail instead. It was slow and expensive, but I wonder how would our grandparents react if someone told them that their mail has been intercepted and examined by various third parties. This is some kind of scenario you would see in movies about totalitarian governments or other kinds of dystopia, but that&rsquo;s exactly what happens with many emails now.</p> <p>It&rsquo;s almost impossible to use Internet if you don&rsquo;t have an email. We type our email addresses in our CVs, we use email to create and manage bank accounts, and we use it to discuss our most important private matters. This information shouldn&rsquo;t be accessible to anyone except the people who participate in our conversations.</p> <h2 id="hard-solution-self-hosted-email-servers">Hard Solution: Self Hosted Email Servers</h2> <p>Technically, you can set up your own email server. The problem is: it&rsquo;s not easy, even for a person with a computer science degree. I&rsquo;m not discouraging anyone from trying to set up their own email service but let&rsquo;s say it&rsquo;s not for everyone, and it would be nice to have a solution that is accessible to a broader audience.</p> <p>Email servers are no different from the other servers when it comes to maintenance. You can rent your own server for few bucks a month, but it would likely be less stable and secure than a hosted email service. It would also burn tens or even hundreds of hours of your time for initial setup and maintenance. Self-hosting isn&rsquo;t easy nor cheap, I doubt it will ever be a viable option when it comes to solving email privacy problem.</p> <h2 id="easier-solution-email-services-with-zero-access-policies">Easier Solution: Email Services With Zero Access Policies</h2> <p>There are a few email providers who offer to store your emails in an encrypted form. ProtonMail is one example, but there are many others. Using those services is not different from using a Gmail account, but they won&rsquo;t sell your data to anyone, they don&rsquo;t even have it in an unencrypted form. The added benefit of not storing unencrypted emails is the fact that it makes such services more hacker-proof. Let&rsquo;s say a group of hackers managed to steal a bunch of emails from an encrypted email service. They wouldn&rsquo;t be able to read them without your password, and you&rsquo;re the only one who knows it.</p> <p>Switching to an email provider that has a zero access policy is the easiest and most effective way to make your email communications more private. Just don&rsquo;t forget that more privacy comes with more responsibility, so you have to pick a good password and make sure you don&rsquo;t lose it. Zero access policy means that your password is the only key to your data, and your email provider wouldn&rsquo;t be able to restore your emails if you lose or forget your password.</p> <h2 id="we-need-a-better-protocol">We Need a Better Protocol</h2> <p>The reality is, no matter how hard we try, email will never be private enough. You might use a fancy privacy respecting email service or even host your own server, but it still exposes your data if you communicate with people who use a shitty email service such as Gmail.</p> <p>We spend a lot of time in instant messaging apps, and those apps have become huge platforms for pretty much everything. Maybe the best way forward is to gradually move away from email and move our communications to instant messaging apps built upon a single universal protocol, such as <a href="https://matrix.org/">Matrix</a>.</p> <h2 id="conclusion">Conclusion</h2> <p>Email is an essential tool that most of us use every day. Unfortunately, many people are unwillingly and unknowingly exposed to a constant surveillance, but there are ways to opt out and take back control of our data. Cryptography is a wonderful thing and it can and should be used to fix many privacy and security issues of Internet services such as email.</p> Raspberry Pi 4 https://bubelov.com/blog/2019/raspberry-pi-4/ Sat, 24 Aug 2019 00:00:00 +0000 https://bubelov.com/blog/2019/raspberry-pi-4/ <p>I wanted to get my hands on a Raspberry Pi 4 board since its release at the beginning of July, but I had to wait a bit longer because an official Asian reseller had been out of stock up until recently. I ordered Raspberry Pi 4 Desktop Kit as soon as it had become available, and it arrived a few days ago.</p> <p><figure> <a href="https://bubelov.com/blog/2019/raspberry-pi-4/thumb_hu_d6f8014a96a14dc3.webp"> <img src="https://bubelov.com/blog/2019/raspberry-pi-4/thumb_hu_58907a8a2b493f00.webp" alt="" /> </a> </figure></p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#desktop-kit">Desktop Kit</a></li> <li><a href="#starting-up">Starting Up</a></li> <li><a href="#strange-errors-while-updating">Strange Errors While Updating</a></li> <li><a href="#performance-after-the-update">Performance After The Update</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="desktop-kit">Desktop Kit</h2> <p>Raspberry Pi 4 desktop kits come with a main board and a few handy extras. The kit boards are identical to boards that can be bought separately, so the only benefit of a desktop kit is that it includes a bunch of Pi-branded periphery. I needed a power supply, keyboard and mouse, so I&rsquo;ve decided to buy it in a single bundle.</p> <h2 id="starting-up">Starting Up</h2> <p>Raspberry Pi computers are very simple to assemble so there is no need for any guides, but, if you like guides, you have one more reason to buy a desktop kit: it has a big and colorful book explaining every step you need to follow in order to get your Raspberry Pi 4 up and running.</p> <p>I have a 4K monitor and I connected it to my shiny new Raspberry Pi 4 and, to my disappointment, it could only use 1980x1080 resolution. I used the default pre-installed operating system - Raspbian, and I expected it to work in 4K resolution because that was clearly stated in the marketing materials on the Raspberry Pi website, those materials even claimed that Raspberry Pi 4 supports two 4K screens simultaneously without any issues (although the frame rate would be cut to 30 FPS if you decide to go with dual monitors).</p> <p>After a few minutes of exploring my new Raspbian installation, I&rsquo;ve found out that it&rsquo;s possible to enable 4K support in system settings, although I wouldn&rsquo;t recommend anyone to do that: it slows down the system dramatically to the point that it&rsquo;s barely usable.</p> <h2 id="strange-errors-while-updating">Strange Errors While Updating</h2> <p>There were a few times when my system tried to update itself, but it always failed with a cryptic message:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">buster InRelease&#39; changed its &#39;Suite&#39; value from &#39;testing&#39; to &#39;stable&#39;</span></span></code></pre></div> <p>I decided to open a Terminal app and to try to update the system from a command line. Raspbian is a part of Debian family of Linux distributions, and the default tool that is used to update such systems is <code>apt</code>:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">sudo apt update</span></span></code></pre></div> <p>Here is what I&rsquo;ve got:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Get:1 http://raspbian.raspberrypi.org/raspbian buster InRelease [15.0 kB] </span></span><span class="line"><span class="cl">Get:2 http://archive.raspberrypi.org/debian buster InRelease [25.2 kB] </span></span><span class="line"><span class="cl">Get:3 http://archive.raspberrypi.org/debian buster/main armhf Packages [222 kB] </span></span><span class="line"><span class="cl">Reading package lists... Done </span></span><span class="line"><span class="cl">E: Repository &#39;http://raspbian.raspberrypi.org/raspbian buster InRelease&#39; changed its &#39;Suite&#39; value from &#39;testing&#39; to &#39;stable&#39; </span></span><span class="line"><span class="cl">N: This must be accepted explicitly before updates for this repository can be applied. See apt-secure(8) manpage for details.</span></span></code></pre></div> <p>It was the same error that I was getting with the graphical user interface. It looked like the package index file that was cached on my Raspberry Pi 4 was somehow incompatible with the package index that <code>apt update</code> tries to fetch to get the updates so I decided to check the update sources that the system used:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">less /etc/apt/sources.list</span></span></code></pre></div> <p>Here is the list of sources that I got:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">deb http://raspbian.raspberrypi.org/raspbian/ buster main contrib non-free rpi </span></span><span class="line"><span class="cl"><span class="c1"># Uncomment line below then &#39;apt-get update&#39; to enable &#39;apt-get source&#39;</span> </span></span><span class="line"><span class="cl"><span class="c1"># deb-src http://raspbian.raspberrypi.org/raspbian/ buster main contrib non-free rpi</span></span></span></code></pre></div> <p>By following the source URL (<a href="http://raspbian.raspberrypi.org/raspbian/dists/buster/">http://raspbian.raspberrypi.org/raspbian/dists/buster/</a>) I was able to find the package list that Raspbian uses to get the update info and it started with the following metadata:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Origin: Raspbian </span></span><span class="line"><span class="cl">Label: Raspbian </span></span><span class="line"><span class="cl">Suite: stable </span></span><span class="line"><span class="cl">Codename: buster </span></span><span class="line"><span class="cl">Date: Sat, 24 Aug 2019 10:49:10 UTC </span></span><span class="line"><span class="cl">Architectures: armhf </span></span><span class="line"><span class="cl">Components: main contrib non-free rpi firmware </span></span><span class="line"><span class="cl">Description: Debian armhf testing distribution for Raspberry Pi</span></span></code></pre></div> <p>The local package index looked like this:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">less /var/lib/apt/lists/raspbian.raspberrypi.org_raspbian_dists_buster_InRelease</span></span></code></pre></div> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Origin: Raspbian </span></span><span class="line"><span class="cl">Label: Raspbian </span></span><span class="line"><span class="cl">Suite: testing </span></span><span class="line"><span class="cl">Codename: buster </span></span><span class="line"><span class="cl">Date: Fri, 07 Jun 2019 22:40:16 UTC </span></span><span class="line"><span class="cl">Architectures: armhf </span></span><span class="line"><span class="cl">Components: main contrib non-free rpi firmware </span></span><span class="line"><span class="cl">Description: Debian armhf testing distribution for Raspberry Pi</span></span></code></pre></div> <p>That&rsquo;s essentially what that error tried to tell me: <code>apt update</code> blocks any updates if the <code>Suite</code> field is changed on the remote end. That&rsquo;s a shame that the GUI don&rsquo;t resolve this issue automatically but at least it&rsquo;s easy to resolve from the terminal. Luckily for me, <code>apt-get update</code> has a special parameter called <code>--allow-releaseinfo-change</code> which allows it to resolve metadata conflicts by opting for a &ldquo;remote&rdquo; option automatically:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">sudo apt-get update --allow-releaseinfo-change</span></span></code></pre></div> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Hit:1 http://archive.raspberrypi.org/debian buster InRelease </span></span><span class="line"><span class="cl">Get:2 http://raspbian.raspberrypi.org/raspbian buster InRelease [15.0 kB] </span></span><span class="line"><span class="cl">Get:3 http://raspbian.raspberrypi.org/raspbian buster/main armhf Packages [13.0 MB] </span></span><span class="line"><span class="cl">Get:4 http://raspbian.raspberrypi.org/raspbian buster/contrib armhf Packages [58.7 kB] </span></span><span class="line"><span class="cl">Fetched 13.1 MB in 2min 25s (90.1 kB/s) </span></span><span class="line"><span class="cl">Reading package lists... Done </span></span><span class="line"><span class="cl">N: Repository &#39;http://raspbian.raspberrypi.org/raspbian buster InRelease&#39; changed its &#39;Suite&#39; value from &#39;testing&#39; to &#39;stable&#39;</span></span></code></pre></div> <p>The last step that is necessary to get all the system updates is to call the <code>apt upgrade</code> command which reads updated package index and downloads new dependencies:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">sudo apt upgrade</span></span></code></pre></div> <p>Usually it&rsquo;s a good idea to reboot your system after a big upgrade. Turning the PC off and then turning it on again is still the easiest and most effective way of solving many technical problems.</p> <h2 id="performance-after-the-update">Performance After The Update</h2> <p>After I updated my system, it started to work a lot more like it was promised on the Raspberry Pi website: the screen switched to 4K mode without any slowdowns and overheating, system UI became easily configurable for high density screens and video decoding performance had improved significantly. I still can&rsquo;t play a 4K video in VLC player but everything else works unexpectedly well.</p> <h2 id="conclusion">Conclusion</h2> <p>I&rsquo;m very impressed with the capabilities of Raspberry Pi, it can really replace a PC for a wide range of tasks. The only problem with Raspberry Pi 4 now is that its software lags behind its hardware capabilities, but it&rsquo;s not critical since the software can be easily improved in future updates and the hardware potential of Raspberry Pi 4 is simply amazing.</p> Trip to Girona https://bubelov.com/blog/2019/girona/ Fri, 23 Aug 2019 00:00:00 +0000 https://bubelov.com/blog/2019/girona/ <p>I wasn&rsquo;t aware of the existence of this city until I had to book a ticket from Barcelona to Moscow. Many ticket aggregators showed a few very cheap options, but they also showed warning icons nearby them, informing me that a departure airport is not exactly in Barcelona but in Girona, a little town nearby. That was the first time I heard about this city, and I decided to spend a few nights there before coming back to Moscow.</p> <p><figure> <a href="https://bubelov.com/blog/2019/girona/thumb_hu_6d4a97d4379f26ce.webp"> <img src="https://bubelov.com/blog/2019/girona/thumb_hu_41e8a21c634d54db.webp" alt="" /> </a> </figure></p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#how-to-get-to-girona-from-barcelona">How to Get to Girona From Barcelona</a></li> <li><a href="#arrival">Arrival</a></li> <li><a href="#girona-city-walls">Girona City Walls</a></li> <li><a href="#main-street">Main Street</a></li> <li><a href="#getting-to-girona-airport">Getting to Girona Airport</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="how-to-get-to-girona-from-barcelona">How to Get to Girona From Barcelona</h2> <p>There are many ways to get to Girona and I decided to use a train. I always give preference to a train commute, where possible, because it&rsquo;s extremely convenient to get from a city center A to a city center B without complex airport check-ins and having to negotiate with nasty taxi drivers.</p> <p>Luckily for me, a railway station I needed was within a walking distance from my hostel in the city center. That&rsquo;s one of the greatest features of Barcelona: anything you would need is just a walking distance away. This railway station is called <a href="https://www.openstreetmap.org/node/2158044594">Barcelona-Sants</a>, and it&rsquo;s located nearby Plaça d&rsquo;Espanya. You can buy a ticket on arrival with one of the station&rsquo;s payment terminals. I was a bit surprised with a price of €32 for a 40-minute ride and the ticket class was suspiciously marked as &ldquo;tourist&rdquo;. I didn&rsquo;t have time to check what does it mean but I guess it&rsquo;s some kind of price discrimination for foreigners which is not uncommon in many countries, and I&rsquo;m sure that the Spanish people pay much less for the same rides.</p> <h2 id="arrival">Arrival</h2> <p>The railway station of Girona is conveniently located nearby the city center, so it would probably take you no more than 15 minutes walking to check in to any hotel or hostel in the center of Girona. As I walked to my booked apartment, I had crossed the most popular place in Girona: a small river with a few crossing bridges:</p> <p><figure> <a href="https://bubelov.com/blog/2019/girona/bridge_hu_c1d5e8776d9b5e4b.webp"> <img src="https://bubelov.com/blog/2019/girona/bridge_hu_91e0558089750c99.webp" alt="" /> </a> <figcaption>Bridge</figcaption> </figure></p> <p>One of those bridges was made by the Eiffel company, and it&rsquo;s not hard to find out which one: it looks somewhat like the Eiffel tower. The area nearby those bridges is very popular in the evenings, and you can find a lot of shops, cafes and restaurants there.</p> <h2 id="girona-city-walls">Girona City Walls</h2> <p>The city center of Girona is surrounded by walls, and they offer great views on the city:</p> <p><figure> <a href="https://bubelov.com/blog/2019/girona/walls_hu_9a22d9d31666c4e4.webp"> <img src="https://bubelov.com/blog/2019/girona/walls_hu_9d78783810109904.webp" alt="" /> </a> <figcaption>Walls</figcaption> </figure></p> <p><figure> <a href="https://bubelov.com/blog/2019/girona/cathedral_hu_294139e1b6f7a32a.webp"> <img src="https://bubelov.com/blog/2019/girona/cathedral_hu_156e128b5b01e3f5.webp" alt="" /> </a> <figcaption>Cathedral</figcaption> </figure></p> <p><figure> <a href="https://bubelov.com/blog/2019/girona/city_hu_6026ea744b1ca0c6.webp"> <img src="https://bubelov.com/blog/2019/girona/city_hu_83ebbc8fa3e44125.webp" alt="" /> </a> <figcaption>City</figcaption> </figure></p> <p><figure> <a href="https://bubelov.com/blog/2019/girona/outside_hu_caff1569197b581d.webp"> <img src="https://bubelov.com/blog/2019/girona/outside_hu_4dbfab84024c2d0a.webp" alt="" /> </a> <figcaption>Outside the walls</figcaption> </figure></p> <h2 id="main-street">Main Street</h2> <p>All the action happens on the main streets of Girona, at both ends of the bridges. The &ldquo;internal&rdquo; side of the city is older and more popular and the &ldquo;external&rdquo; side is more modern, and it&rsquo;s packed with a lot of commercial buildings and condos.</p> <p>I liked the &ldquo;old&rdquo; side of the river more: it has a lot of bars and cafes with their tables outside, and it also has a few bookshops with quite an interesting selection of books. One of the books I&rsquo;ve found exciting is &ldquo;History of Catalonia&rdquo; by Sobrequés i Callicó, Jaume. The author neatly explains the reasons for Catalonia&rsquo;s struggle for independence and this book also filled a few gaps in my knowledge of the history of Spain starting from the age of crusades up until the years of Franco&rsquo;s dictatorship.</p> <p><figure> <a href="https://bubelov.com/blog/2019/girona/catalonia_hu_d6c7c06376ec667c.webp"> <img src="https://bubelov.com/blog/2019/girona/catalonia_hu_eca2c3c5b3b90604.webp" alt="" /> </a> <figcaption>Catalonian flag</figcaption> </figure></p> <h2 id="getting-to-girona-airport">Getting to Girona Airport</h2> <p>The easiest and cheapest way to get to Girona airport is to ride a bus from the train station. The bus terminals are located at the basement floor of the station, so you have to enter the station first and then take the elevator down. It&rsquo;s possible to pay cash inside the bus so there is no need to buy a ticket in advance.</p> <h2 id="conclusion">Conclusion</h2> <p>Girona is a nice city to visit for a couple of days. It&rsquo;s one of those places that is really great to stay at, until you understand that there is nothing else to see, and it&rsquo;s time to move on. I would definitely check Girona again if I happen to be in this part of Spain in the future.</p> Trip to Barcelona https://bubelov.com/blog/2019/barcelona/ Tue, 06 Aug 2019 00:00:00 +0000 https://bubelov.com/blog/2019/barcelona/ <p>I&rsquo;ve heard a lot of good things about Barcelona and I have always wanted to visit it. My trip to Barcelona started rather unexpectedly, I just wanted to get a Schengen visa, and I was advised to pick Spain as a country of first entrance because Spain tends to issue longer visas which can be re-used later to visit other EU countries.</p> <p><figure> <a href="https://bubelov.com/blog/2019/barcelona/thumb_hu_94f63ef9fd986b42.webp"> <img src="https://bubelov.com/blog/2019/barcelona/thumb_hu_a1717a386ca46e66.webp" alt="" /> </a> <figcaption>Plaça d&rsquo;Espanya</figcaption> </figure></p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#preparation">Preparation</a></li> <li><a href="#hostel">Hostel</a></li> <li><a href="#getting-by">Getting By</a></li> <li><a href="#how-to-stay-online">How to Stay Online</a></li> <li><a href="#picasso-museum">Picasso Museum</a></li> <li><a href="#national-palace">National Palace</a></li> <li><a href="#antoni-gaudí">Antoni Gaudí</a></li> <li><a href="#food">Food</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="preparation">Preparation</h2> <p>I&rsquo;ve had an unpleasant experience with filing for a Schengen visa before, it was at a Czech embassy and they gave me a visa with a validity period of two weeks which is bogus, so I&rsquo;ve decided to never apply for such a visa in countries that have a bad track record in terms of the length of visas they issue. Spain, on the contrary, is known for its generous visa policy, so I decided to apply for my new visa there and I wasn&rsquo;t disappointed.</p> <p>That said, I had to fly to Spain and my sights fell on Barcelona as my first destination. The flight from Moscow to Barcelona took about 4.5 hours and the first thing I noticed after arrival is how different the weather in Barcelona compared to Moscow. The weather in Moscow was not particularly pleasant this summer but the weather in Barcelona was perfect: very sunny and no rains. With such weather, the only thing I needed was just a small bag with a few days stock of light clothes, so I didn&rsquo;t take any baggage with me.</p> <h2 id="hostel">Hostel</h2> <p>I managed to avoid hostels for a couple of years but the hotel prices in Barcelona motivated me to reconsider. I wouldn&rsquo;t say that the hostels are cheap in Barcelona, they start from $40 per night, and it&rsquo;s especially shocking for someone who lives in Asia. For the same price, you can find a decent hotel in Thailand, but $40 per night is still much better than $120+ which you have to pay for a hotel room in Barcelona.</p> <p>The hostel I stayed in is called &ldquo;Safestay Passeig de Gracia&rdquo;, and it was pretty nice, except the fact that I had to buy my own lock, and it turned out that my stay wasn&rsquo;t actually as safe as the name of this hostel implies: some guy on the same floor with me left his iPhone unattended for a few minutes, and the device was gone without any traces.</p> <h2 id="getting-by">Getting By</h2> <p>Barcelona has a convenient system of public transport and there are many ways to get to the most interesting places in town. After my arrival, I&rsquo;ve decided to buy an unlimited ticket to Barcelona subway, and you can actually use the subway to get from the airport to any hotel downtown. It&rsquo;s also possible to use a bus or taxi in order to hit town.</p> <p>Interestingly, the same subway ticket is also valid for funicular rides to Montjuïc Castle, one more reason to check it out. The castle itself is pretty small, but it offers a nice view on the city, something like this:</p> <p><figure> <a href="https://bubelov.com/blog/2019/barcelona/wide-view_hu_31e01881ad4dd1df.webp"> <img src="https://bubelov.com/blog/2019/barcelona/wide-view_hu_ab75528b582c4f48.webp" alt="" /> </a> <figcaption>View from Montjuïc Castle</figcaption> </figure></p> <h2 id="how-to-stay-online">How to Stay Online</h2> <p>There are plenty of small electronic shops everywhere, and they can sell you a pre-paid mobile data plan for €15-€30, depending on your needs. I picked 10 GB option for €30, and it was an overkill, but it relieved me of any worries about any traffic limits I could potentially hit, so it was worth it.</p> <h2 id="picasso-museum">Picasso Museum</h2> <p>I&rsquo;m a big fan of museums, so the first place I visited in Barcelona was the Picasso museum. The exhibition there walked me through the life of Picasso, and it also showed some of his works. I&rsquo;m not that interested in art, so I can&rsquo;t really comment on that, but I enjoy studying history and this exhibition gave me a lot of information on the historical context of Picasso&rsquo;s life. Contrary to my previous knowledge, Picasso wasn&rsquo;t only a great painter, but he also was an established sculptor and photographer.</p> <p>While you are there, it might be worthwhile to check the Gothic Quarter and the street nearby Gambrinus Lobster statue:</p> <p><figure> <a href="https://bubelov.com/blog/2019/barcelona/gambrinus-lobster_hu_82a1ba34f4016830.webp"> <img src="https://bubelov.com/blog/2019/barcelona/gambrinus-lobster_hu_a8effff81cdc0333.webp" alt="" /> </a> <figcaption>Gambrinus Lobster</figcaption> </figure></p> <h2 id="national-palace">National Palace</h2> <p>National palace is a beautiful building nearby Plaça d&rsquo;Espanya. It has a good view on the city, and it has a lot of spots where you can hide from The Sun.</p> <p><figure> <a href="https://bubelov.com/blog/2019/barcelona/national-palace_hu_2201ce9d7d7d4ba1.webp"> <img src="https://bubelov.com/blog/2019/barcelona/national-palace_hu_73465bc8e0362ecc.webp" alt="" /> </a> <figcaption>National palace</figcaption> </figure></p> <p>Here is the view that covers Plaça d&rsquo;Espanya and the most of the city:</p> <p><figure> <a href="https://bubelov.com/blog/2019/barcelona/placa_hu_30c5a04f91e7c1cd.webp"> <img src="https://bubelov.com/blog/2019/barcelona/placa_hu_c8aa2d04e701c826.webp" alt="" /> </a> <figcaption>Plaça d&rsquo;Espanya</figcaption> </figure></p> <h2 id="antoni-gaudí">Antoni Gaudí</h2> <p>Antoni Gaudí was a Spanish architect who left quite a footprint in Barcelona. His most famous work is La Sagrada Familia which is still under construction and expected to be finished by 2026. I also checked out a couple of residential buildings that he created: Casa Batlló and Casa Milá. Most of Gaudí&rsquo;s works are walking distance away from the downtown, so it&rsquo;s easy to visit them even if you have a limited time at your disposal.</p> <h2 id="food">Food</h2> <p>Catalonia, as well as Spain and Portugal are perfect places to try an Iberian ham. I ordered it several times, and I was surprised with how the waiters served it. Once, the waitress just gave me a plate with ham, a few toasts and a whole tomato. I&rsquo;m an engineer, so I&rsquo;ve quickly figured out that I have to place ham on the toasts, but I was unable to figure out what to do with that tomato. The waitress was out of reach, so I had to ask Google.</p> <p>It turned out, Catalonians like to toast their bread first and then polish it with fresh tomato to make one side of the toast a bit softer and to their taste. I tried to do that and it tasted great.</p> <p>As of the other food in Barcelona, I&rsquo;ve found it rather ambiguous. Let&rsquo;s take tapas as an example: anything can be called tapas, as long as the portion is small, so it&rsquo;s not a particular recipe, it&rsquo;s more like a format. I&rsquo;ve tried a few tapas places and they were pretty great. I liked the fact that due to a smaller serving size it&rsquo;s possible to try many dishes without the worry that your stomach will explode.</p> <h2 id="conclusion">Conclusion</h2> <p>I really liked Barcelona and I hope that I&rsquo;ll be able to visit it again. The city is very comfortable, the climate is warm, and the people are cheerful and welcoming. Barcelona is a part of Catalonia which considers itself an independent state, and it looks like the majority of Catalonians want independence. It would be interesting to see what happens next, maybe next time I&rsquo;ll need another kind of visa in order to visit Barcelona again.</p> The Road to Serfdom https://bubelov.com/blog/2019/road-to-serfdom/ Thu, 06 Jun 2019 00:00:00 +0000 https://bubelov.com/blog/2019/road-to-serfdom/ <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#preface">Preface</a></li> <li><a href="#quotes">Quotes</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="preface">Preface</h2> <p>Recently I came across a <a href="https://www.youtube.com/watch?v=15idnfuyqXs">1994 interview with Milton Friedman</a> where he talked about working with Friedrich Hayek and how he ended up writing the introduction to one of Hayek’s most popular books: The Road to Serfdom. The video reminded me that I&rsquo;d had this book on my to‑read list for years, so I finally decided it was time.</p> <p>I&rsquo;m not sure why I waited so long to read this book. Maybe it&rsquo;s because the title felt a bit provocative, and I&rsquo;m not a big fan of heated propaganda, even when it promotes ideas I generally support.</p> <p>On the other hand, the title also sounds straight out of a dystopia, and I love dystopian stories. So I had a hunch I wouldn&rsquo;t be disappointed, and in the end, my expectations were right.</p> <p>First, I found the book surprisingly civil, its tone is quite moderate, and it appeals to logic instead of emotion or a shifting sense of &ldquo;justice&rdquo;. The core ideas have stood the test of time and are still relevant today.</p> <p>The book&rsquo;s main goal is to warn against the dangers of collectivism. It carefully explains the similarities between the most economically unfree countries of its time: Germany, the USSR, and Italy, all of which were collectivist regimes that tried to seize control of their economies from private hands and &ldquo;plan&rdquo; their way to success.</p> <p>It&rsquo;s clear that the intellectual context and even the core argumentative structure of this book were shaped by Hayek&rsquo;s conversations with Popper and by reading Popper&rsquo;s manuscript for <a href="https://bubelov.com/blog/2021/open-society/">The Open Society and Its Enemies</a> (published in 1945, one year after The Road to Serfdom).</p> <h2 id="quotes">Quotes</h2> <p>The author neatly reminds us that having good intentions doesn&rsquo;t always translate to having a good outcome:</p> <blockquote> <p>We are ready to accept almost any explanation of the present crisis of our civilization except one: that the present state of the world may be the result of genuine error on our own part and that the pursuit of some of our most cherished ideals has apparently produced results utterly different from those which we expected.</p> </blockquote> <p>I liked Hayek&rsquo;s take on the role on certain biases which may lead us to believe that planned economy might work:</p> <blockquote> <p>In our predilections and interests we are all in some measure specialists. And we all think that our personal order of values is not merely personal but that in a free discussion among rational people we would convince the others that ours is the right one.</p> </blockquote> <p>Another interesting idea expressed in this book is that we should be really careful with what the word &ldquo;planning&rdquo; actually means. This word sounds pleasing, who don&rsquo;t like certainty and order? There is no doubt that planning is an effective tool but don&rsquo;t we do it on the individual level already? What &ldquo;planned economy&rdquo; means is centralization of planning and such a system just can&rsquo;t afford to respect everyone&rsquo;s best interests, so it doesn&rsquo;t make sense to give up our planning power and make it centralized which opens it to abuse by a single person or a small group of ruling elite:</p> <blockquote> <p>If, on the other hand, the state were to direct the individual’s actions so as to achieve particular ends, its action would have to be decided on the basis of the full circumstances of the moment and would therefore be unpredictable. Hence the familiar fact that the more the state “plans,” the more difficult planning becomes for the individual.</p> </blockquote> <p>The problem of centralization is one of the core topics of this book, there are so many problems that arise from centralization that we should think twice before allowing it to happen:</p> <blockquote> <p>What our generation has forgotten is that the system of private property is the most important guaranty of freedom, not only for those who own property, but scarcely less for those who do not. It is only because the control of the means of production is divided among many people acting independently that nobody has complete power over us, that we as individuals can decide what to do with ourselves.</p> </blockquote> <blockquote> <p>To split or decentralize power is necessarily to reduce the absolute amount of power, and the competitive system is the only system designed to minimize by decentralization the power exercised by man over man.</p> </blockquote> <p>Another interesting take on the nature of our attitude to free market is that we usually don&rsquo;t trust the things we don&rsquo;t understand and no one understands the market completely which makes us suspicious of its &ldquo;agenda&rdquo;:</p> <blockquote> <p>And it fails to see that, unless this complex society is to be destroyed, the only alternative to submission to the impersonal and seemingly irrational forces of the market is submission to an equally uncontrollable and therefore arbitrary power of other men.</p> </blockquote> <p>The market can be unpredictable on the short term, and it&rsquo;s normal to be worried about it. Our jobs might be rendered obsolete in just a few years, and it can be painful to adjust to a new reality. Hayek thinks that the most freedom-preserving way to add more stability and protect us from the wild swings of the market is to introduce some kind of universal basic income. He also thinks that we should end the system of targeted privileges because it decreases the risks of certain groups by increasing the risks for the rest of society:</p> <blockquote> <p>Let a uniform minimum be secured to everybody by all means; but let us admit at the same time that with this assurance of a basic minimum all claims for a privileged security of particular classes must lapse, that all excuses disappear for allowing groups to exclude newcomers from sharing their relative prosperity in order to maintain a special standard of their own.</p> </blockquote> <h2 id="conclusion">Conclusion</h2> <p>Hayek&rsquo;s core thesis, that collectivist economic planning inevitably leads to a loss of liberty, is less a proven historical fact than a powerful political warning. He may have oversimplified the complex origins of fascism and communism, but he correctly exposed the tyrannical logic inherent in the utopian planner&rsquo;s mindset.</p> Installing Ubuntu on a MacBook Pro A1398 https://bubelov.com/blog/2019/ubuntu-macbook-2012/ Sun, 19 May 2019 00:00:00 +0000 https://bubelov.com/blog/2019/ubuntu-macbook-2012/ <p>It&rsquo;s been a while since I used Ubuntu or any other Linux distribution outside of Docker containers, so I&rsquo;ve decided to give it a go and see if it&rsquo;d be suitable for my everyday needs. The laptop I chose is my old MacBook Pro A1398, and I went with Ubuntu 18.04 as it is the most stable Ubuntu version at the moment. As I suspected, it didn&rsquo;t work right out of the box, but the problems I had to deal with were rather minor, and I was expecting worse.</p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#preparation">Preparation</a></li> <li><a href="#issue-with-wi-fi">Issue With Wi-Fi</a></li> <li><a href="#issue-with-nvidia-driver">Issue With Nvidia Driver</a></li> <li><a href="#screen-freeze">Screen Freeze</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="preparation">Preparation</h2> <p>The most convenient way to install Ubuntu on a MacBook is to download an ISO image from the <a href="https://www.ubuntu.com/download/desktop">official website</a>.</p> <p>Now that we have an ISO file, we need to burn it to a USB drive and after burning an ISO file, you can turn off your MacBook and turn it on again while holding the <code>Alt</code> key. That way, you will be asked to choose a drive you&rsquo;d like to boot from. Choose your flash drive and if you have any doubts on which icon represents it, try unplugging it and plugging it again, it should force the boot menu to animate this drive&rsquo;s icon.</p> <p>After you boot from your flash drive, it will ask you whether you want to install Ubuntu now or just want to try it without installing. I&rsquo;d recommend you try it first because this way, you can actually see what you are going to get, and it just makes more sense to set up the internet connection before you start the installation process because it will allow you to download all the system updates and drivers right on the spot.</p> <h2 id="issue-with-wi-fi">Issue With Wi-Fi</h2> <p>I&rsquo;ve never had an Ubuntu with the working network drivers out of the box, even in the old RJ-45 times, so I wasn&rsquo;t surprised that the system did not recognize my MacBook&rsquo;s Wi-Fi module. Luckily for me, the Bluetooth module was recognized and worked pretty well, so I just tethered the Internet connection from my Android phone and that gave me enough bandwidth to allow the installer to fetch all the updates and drivers during the installation. Once I installed Ubuntu on my laptop, I just opened the &ldquo;Software &amp; Updates&rdquo; app and clicked on the &ldquo;Additional Drivers&rdquo; tab. This screen displays all the driver options that are available for your laptop, and it allows you to pick the proprietary Broadcom driver that should allow you to use Wi-Fi.</p> <p><figure> <a href="https://bubelov.com/blog/2019/ubuntu-macbook-2012/drivers_hu_5285ebfcb91a847a.webp"> <img src="https://bubelov.com/blog/2019/ubuntu-macbook-2012/drivers_hu_464aa906de7f6a0c.webp" alt="" /> </a> <figcaption>Software &amp; Updates app</figcaption> </figure></p> <h2 id="issue-with-nvidia-driver">Issue With Nvidia Driver</h2> <p>After installing the Broadcom driver, I had a temptation to try proprietary Nvidia driver for my video card. Well, it was a grave mistake. That driver seemed to work fine at first but then I found out that I couldn&rsquo;t change the screen brightness, and it was especially annoying in the presence of the daylight since the &ldquo;fixed&rdquo; value was far from the maximum brightness that my display could produce, so I could hardly see anything on the screen.</p> <p>I didn&rsquo;t worry about that since the default open source video card driver seemed to work fine, so I&rsquo;ve decided to switch back to the default option. I pressed &ldquo;Apply Changes&rdquo; and rebooted my laptop, but I wasn&rsquo;t able to start my Ubuntu again! The only thing I saw was a black screen with the message like this:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">dev/sda1: clean, n/n files, n/n blocks </span></span></code></pre></div><p>To resolve this issue, I had to boot to console in the recovery mode and remove Nvidia driver manually. First thing you need to do if you&rsquo;re in the same situation is to hold <code>Esc</code> key while booting and once you manage to get to the terminal, just type:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo apt-get purge nvidia* </span></span></code></pre></div><p>Once I did that I was able to log into my Ubuntu and my brightness controls seemed to work just fine.</p> <h2 id="screen-freeze">Screen Freeze</h2> <p>I&rsquo;ve been using Ubuntu for three days, and it seems to be more or less stable, but it did become unresponsive once. I&rsquo;m not sure if it was caused by the previous issue with the video driver, or it&rsquo;s a different issue, but I wouldn&rsquo;t feel too safe running an Ubuntu on a MacBook Pro. This reminded me how important it is to have an auto save feature enabled in text editors and to back up your data every day.</p> <h2 id="conclusion">Conclusion</h2> <p>It&rsquo;s not that hard to get Ubuntu 18.04 up and running on a MacBook Pro, but you have to be careful with the drivers. Personally, I&rsquo;m not planning to use Ubuntu on my MacBook and I&rsquo;ve already ordered Dell XPS 13 9380 for that purpose because I think that it will be a better fit for a Linux system. I wouldn&rsquo;t recommend using Ubuntu on MacBook Pro in most of the cases since it plays much better with macOS which is a pretty solid system for most of the tasks but Apple might abandon OS updates for MacBook Pro A1398 pretty soon and that&rsquo;s where Linux might be a good alternative to stay up to date in terms of software and security updates.</p> Trip to Samui and Pha Ngan https://bubelov.com/blog/2019/trip-to-samui-pha-ngan/ Sun, 05 May 2019 00:00:00 +0000 https://bubelov.com/blog/2019/trip-to-samui-pha-ngan/ <p>Ko Samui is an island on the east cost of Thailand, and it&rsquo;s the second-biggest island in Thai kingdom. Pha Ngan is a much smaller island not far from Ko Samui, and it&rsquo;s mostly known for its Full Moon Party. I&rsquo;d got a lot of positive feedback about those islands from my friends, so I finally decided to visit Ko Samui and Pha Ngan for a few days.</p> <p><figure> <a href="https://bubelov.com/blog/2019/trip-to-samui-pha-ngan/thumb_hu_d3c4b14638b09f3.webp"> <img src="https://bubelov.com/blog/2019/trip-to-samui-pha-ngan/thumb_hu_693e5fd99c42eeb6.webp" alt="" /> </a> </figure></p> <h2 id="surat-thani">Surat Thani</h2> <p>I was surprised that it&rsquo;s possible to transfer cars to any of those islands, and the only thing you have to do is to drive to Surat Thani and take a ferry from there to either Ko Samui or Pha Ngan. Our car trip From Phuket to Surat Thani took about 4.5 hours, and we needed an additional 1.5 hours to get from Surat Thani to Samui by a ferry.</p> <p>Buying ferry tickets wasn&rsquo;t as easy as we expected because the sales office employees told us that it is not possible to buy a one-way ticket. Eventually, we were able to convince them that they should sell us those tickets because their website allowed this type of booking. One of the first lessons I&rsquo;ve learned after moving to Thailand is that you should never assume that things will work as expected.</p> <p>After we booked a ferry at Surat Thani, we didn&rsquo;t want to waste time trying to pick the best place to have lunch, so we picked a restaurant called Vanilla, pretty much randomly. Luckily for us, it was a fantastic place: the atmosphere was great, the service was superb, and the food far outstripped our expectations. There are many gems like this scattered across the other not so popular regions of Thailand and I think that those places are probably less spoiled by the recent boom of tourism, so they put more effort in delivering best value for money.</p> <h2 id="ferries">Ferries</h2> <p>Using a ferry in Thailand is always a gamble because you never know if you will get a comfortable ship, or a floating dumpster even if the price is the same. The first ferry we took (Surat Thani -&gt; Samui) was more or less average. It provided decent Sun protection, and it had a basic ventilation system on board. The second ferry (Samui -&gt; Pha Ngan) was great: the chairs were super comfortable, and the passenger room was well conditioned during the trip.</p> <p>It&rsquo;s painful to remember our third ferry (Pha Ngan -&gt; Surat Thani) because the passenger room didn&rsquo;t have any window protection from the Sun and all the fans were broken. Luckily for us, we departed at 5 PM and the Sun tends to set quite early in Thailand, so it felt less horrible after 6:30 PM.</p> <h2 id="samui">Samui</h2> <p>Picking a decent hotel is another kind of gamble, especially in Thailand. First, hotel &ldquo;stars&rdquo; mean nothing so don&rsquo;t even bother considering them as a factor. After browsing through a few dozens of hotels, I&rsquo;ve noticed a familiar name: &ldquo;Ibis&rdquo;, and I thought that it might be wise to stick to a brand name because most of the brands try to deliver consistent and predictable experience in every location.</p> <p>Well, it was a horrible mistake: the service was extremely slow, and their receptionists tried to take our payment cards &ldquo;hostage&rdquo; during our whole stay. We asked them for clarification, and we tried to explain that it&rsquo;s a gross violation of the terms of service of any payment card. The card should never leave the sight of the cardholder during the payment process, but the Ibis employees didn&rsquo;t seem to care. Finally, they backed off, but I wouldn&rsquo;t advise anyone to use this hotel.</p> <p>The beach nearby our hotel turned out to be the worst beach on the island. Anyway, at least our breakfast was nice, and we had a car to explore the island. There are many other beaches in Samui, and they are actually pretty nice. Here are a few pictures:</p> <p><figure> <a href="https://bubelov.com/blog/2019/trip-to-samui-pha-ngan/lamai-beach_hu_64156ec2d6314e32.webp"> <img src="https://bubelov.com/blog/2019/trip-to-samui-pha-ngan/lamai-beach_hu_4190b59164c0fcfd.webp" alt="" /> </a> <figcaption>Lamai Beach</figcaption> </figure></p> <p><figure> <a href="https://bubelov.com/blog/2019/trip-to-samui-pha-ngan/chaweng-beach_hu_38ddebdd7542a9de.webp"> <img src="https://bubelov.com/blog/2019/trip-to-samui-pha-ngan/chaweng-beach_hu_bda6893b6c36e562.webp" alt="" /> </a> <figcaption>Chaweng Beach</figcaption> </figure></p> <p><figure> <a href="https://bubelov.com/blog/2019/trip-to-samui-pha-ngan/crystal-bay_hu_a323fbc981cc90f5.webp"> <img src="https://bubelov.com/blog/2019/trip-to-samui-pha-ngan/crystal-bay_hu_1d5d2611e5d64411.webp" alt="" /> </a> <figcaption>Crystal Bay</figcaption> </figure></p> <p><figure> <a href="https://bubelov.com/blog/2019/trip-to-samui-pha-ngan/crystal-bay-2_hu_2c94779711afc05e.webp"> <img src="https://bubelov.com/blog/2019/trip-to-samui-pha-ngan/crystal-bay-2_hu_1c673583e3d6ba7e.webp" alt="" /> </a> <figcaption>Crystal Bay</figcaption> </figure></p> <h2 id="other-places-in-samui">Other Places in Samui</h2> <p>Speaking of other places, Samui does not look that different from the rest of Thailand. You can find many temples, national parks, contact zoo, safari experience and many other &ldquo;standard&rdquo; tourist attractions. It feels like a smaller and less developed version of Phuket and it has all the basics to stay comfortably for a long time but don&rsquo;t expect to find many shopping malls, decent hospitals or even an Apple Store there.</p> <h2 id="trip-to-pha-ngan">Trip to Pha Ngan</h2> <p>Our next destination was Pha Ngan island. Conveniently for us, we found a ferry company that could transfer us directly form Samui to Pha Ngan. The first impression was great: the district around a port felt comfy and welcoming, the hotel was good and almost all the restaurants nearby had suspiciously high ratings of 4.7 stars and more. We tried a few of those places, and they were actually pretty good.</p> <p>Pha Ngan is a tiny island, mostly known for its Full Moon Party. We were 2 weeks early to the next party, but we decided to go check the &ldquo;party district&rdquo; anyway.</p> <p><figure> <a href="https://bubelov.com/blog/2019/trip-to-samui-pha-ngan/party-district_hu_ab07bf71503e673b.webp"> <img src="https://bubelov.com/blog/2019/trip-to-samui-pha-ngan/party-district_hu_8c56c0343e85525b.webp" alt="" /> </a> <figcaption>Full Moon Party beach</figcaption> </figure></p> <p>Most of the places there were closed but some restaurants and entertainment centers seemed to work every night, despite the obvious lack of visitors.</p> <p>The north-western part of Pha Ngan seemed more alive and more contrasting with both the port area and &ldquo;Full Moon Party&rdquo; beach. This part of the island was filled with yoga schools and their customers: hipsters and hippies. It reminded me of some old movies about the hippie movement in the US. The overall atmosphere there makes you feel &ldquo;off the grid&rdquo;, and it&rsquo;s one of the strangest places I saw in Thailand.</p> <p>There are also a few beaches in that area, but we managed to visit only Secret Beach:</p> <p><figure> <a href="https://bubelov.com/blog/2019/trip-to-samui-pha-ngan/secret-beach_hu_377fdc8b564615ab.webp"> <img src="https://bubelov.com/blog/2019/trip-to-samui-pha-ngan/secret-beach_hu_bb7b465a8867ae4b.webp" alt="" /> </a> <figcaption>Secret Beach</figcaption> </figure></p> <p>It wasn&rsquo;t bad, but it had a lot of stones, so it wasn&rsquo;t easy to get in or get out.</p> <p><figure> <a href="https://bubelov.com/blog/2019/trip-to-samui-pha-ngan/smoke_hu_1bd1ca29be669fd3.webp"> <img src="https://bubelov.com/blog/2019/trip-to-samui-pha-ngan/smoke_hu_8eb8c5c272371d47.webp" alt="" /> </a> <figcaption>View on Pha Ngan from a ferry</figcaption> </figure></p> <p>After a couple of other activities, we&rsquo;ve decided to move back to Surat Thani. As I mentioned before, the ferry was awful, but we were tired enough not to care about that too much.</p> <p><figure> <a href="https://bubelov.com/blog/2019/trip-to-samui-pha-ngan/sunset_hu_e619b7dce4bf5ac2.webp"> <img src="https://bubelov.com/blog/2019/trip-to-samui-pha-ngan/sunset_hu_e09bdfec0de95316.webp" alt="" /> </a> <figcaption>Sunset</figcaption> </figure></p> <p>The last ferry departs at 5 PM, so you can see the sunset from it.</p> <h2 id="conclusion">Conclusion</h2> <p>Samui and Pha Ngan are nice islands to visit for a few days. I enjoyed most of the trip, and I&rsquo;m thinking of going back to Pha Ngan to visit Full Moon Party and to do some SCUBA diving nearby the island.</p> Using GeoIP2 Databases With NGINX https://bubelov.com/blog/2019/nginx-geoip/ Tue, 16 Apr 2019 00:00:00 +0000 https://bubelov.com/blog/2019/nginx-geoip/ <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#warning">WARNING</a></li> <li><a href="#preface">Preface</a></li> <li><a href="#nginx-access-log">NGINX Access Log</a></li> <li><a href="#where-are-our-clients-from">Where Are Our Clients From?</a></li> <li><a href="#connecting-geoip2-database-to-nginx">Connecting GeoIP2 Database to NGINX</a></li> <li><a href="#adding-country-info-to-nginx-access-log">Adding Country Info to NGINX Access Log</a></li> <li><a href="#running-geoip2-enabled-nginx-in-docker">Running GeoIP2 Enabled NGINX in Docker</a></li> <li><a href="#alternative">Alternative</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="warning">WARNING</h2> <p>This is an old article, some approaches may be terribly out of date.</p> <p>Also note that country and city detection based on IP is not as accurate as you might think. Personally, I stopped guessing and no longer need those tools.</p> <h2 id="preface">Preface</h2> <p>I&rsquo;ve been using Google Analytics for a while, but I never liked the side effects of this way of collecting website usage data such as having to install Google&rsquo;s tracking scripts on all of my web pages. Those scripts are bad for both user privacy and website performance, so I&rsquo;ve decided to get rid of them and find an alternative way to collect the data I&rsquo;m interested in. In this post I&rsquo;m going to show how to set up a NGINX web server that can log the location of its clients.</p> <h2 id="nginx-access-log">NGINX Access Log</h2> <p>The default NGINX access log entries have the following format:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">log_format main &#39;$remote_addr - $remote_user [$time_local] &#34;$request&#34; &#39; </span></span><span class="line"><span class="cl"> &#39;$status $body_bytes_sent &#34;$http_referer&#34; &#39; </span></span><span class="line"><span class="cl"> &#39;&#34;$http_user_agent&#34; &#34;$http_x_forwarded_for&#34;&#39;; </span></span></code></pre></div><p>Which produces the following line in your <code>access.log</code> file:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">183.88.21.120 - - [16/Apr/2019:07:03:23 +0000] &#34;GET / HTTP/1.1&#34; 200 612 &#34;-&#34; &#34;Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:66.0) Gecko/20100101 Firefox/66.0&#34; &#34;-&#34; </span></span></code></pre></div><p>As you can see, NGINX provides us with a lot of useful information by default. We can see the IP addresses of our clients as well as their operating systems and their browser info. Unfortunately, there is no information about the geographical location of our visitors. I&rsquo;m not talking about GPS precision of course, but it would be nice to know the country of origin of every incoming request. The country info may be used for analytics as well as for blacklisting certain countries from accessing the web server.</p> <h2 id="where-are-our-clients-from">Where Are Our Clients From?</h2> <p>It&rsquo;s not that hard to find a location of your visitors if you know their IP addresses so the only thing we have to do is to check those IP addresses against the countries&rsquo; database. Luckily for us, such a database is available for free on <a href="https://dev.maxmind.com/geoip/geoip2/geolite2/">MaxMind website</a>.</p> <p>In order to use this database, you need to install the <a href="https://github.com/maxmind/libmaxminddb/">MaxMind DB C library</a> on your webserver. The installation instructions are provided on GitHub and here is how a typical setup might be performed on Ubuntu:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">add-apt-repository ppa:maxmind/ppa </span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">apt install libmaxminddb0 libmaxminddb-dev mmdb-bin </span></span></code></pre></div><p>That&rsquo;s it, now you can use the <code>libmaxminddb</code> utility to get the geo location of any IP address. Here is the example:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">mmdblookup \ </span></span><span class="line"><span class="cl"> --file /usr/share/geoip/GeoLite2-Country.mmdb \ </span></span><span class="line"><span class="cl"> --ip 46.35.64.0 country names en </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> &#34;Yemen&#34; &lt;utf8_string&gt; </span></span></code></pre></div><h2 id="connecting-geoip2-database-to-nginx">Connecting GeoIP2 Database to NGINX</h2> <p>Now it&rsquo;s time to automate our IP address recognition routine by connecting <code>mmdblookup</code> utility to NGINX via <a href="https://github.com/leev/ngx_http_geoip2_module">ngx_http_geoip2_module</a>.</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">git clone https://github.com/leev/ngx_http_geoip2_module /ngx_http_geoip2_module </span></span></code></pre></div><p>Next, you need to call the NGINX&rsquo;s <strong>./configure</strong> command with the following parameter:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">--add-dynamic-module=/ngx_http_geoip2_module </span></span></code></pre></div><h2 id="adding-country-info-to-nginx-access-log">Adding Country Info to NGINX Access Log</h2> <p>Now let&rsquo;s open our <code>nginx.conf</code> and add the following lines:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1">#... </span></span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">load_module</span> <span class="s">modules/ngx_http_geoip2_module.so</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1">#... </span></span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">http</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="c1">#... </span></span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="kn">geoip2</span> <span class="s">/usr/share/geoip/GeoLite2-Country.mmdb</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="kn">$geoip2_data_country_code</span> <span class="s">source=</span><span class="nv">$remote_addr</span> <span class="s">country</span> <span class="s">iso_code</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="kn">$geoip2_data_country_name</span> <span class="s">source=</span><span class="nv">$remote_addr</span> <span class="s">country</span> <span class="s">names</span> <span class="s">en</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="kn">log_format</span> <span class="s">main_geo</span> <span class="s">&#39;</span><span class="nv">$remote_addr</span> <span class="s">-</span> <span class="nv">$remote_user</span> <span class="s">[</span><span class="nv">$time_local]</span> <span class="s">&#34;</span><span class="nv">$request&#34;</span> <span class="s">&#39;</span> </span></span><span class="line"><span class="cl"> <span class="s">&#39;</span><span class="nv">$status</span> <span class="nv">$body_bytes_sent</span> <span class="s">&#34;</span><span class="nv">$http_referer&#34;</span> <span class="s">&#39;</span> </span></span><span class="line"><span class="cl"> <span class="s">&#39;&#34;</span><span class="nv">$http_user_agent&#34;</span> <span class="s">&#34;</span><span class="nv">$http_x_forwarded_for&#34;</span> <span class="s">&#39;</span> </span></span><span class="line"><span class="cl"> <span class="s">&#39;</span><span class="nv">$geoip2_data_country_code</span> <span class="nv">$geoip2_data_country_name&#39;</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="kn">access_log</span> <span class="s">/var/log/nginx/access.log</span> <span class="s">main_geo</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1">#... </span></span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>You can see the whole <code>nginx.conf</code> file here: <a href="https://github.com/bubelov/nginx-alpine-geoip2/blob/master/nginx.conf">https://github.com/bubelov/nginx-alpine-geoip2/blob/master/nginx.conf</a></p> <p>The log format defined above is identical to the default log format you can find with most of the NGINX distributions, except that we appended 2 more variables to the end of each log entry: <code>$geoip2_data_country_code</code> and <code>$geoip2_data_country_name</code>. Those variables hold the country info returned by the <code>mmdblookup</code> utility.</p> <h2 id="running-geoip2-enabled-nginx-in-docker">Running GeoIP2 Enabled NGINX in Docker</h2> <p>I&rsquo;ve created this <a href="https://github.com/bubelov/nginx-alpine-geoip2">sample project</a> that shows how to use such a setup in Docker. This project is based on the official <code>nginx-alpine</code> Dockerfile with minimal modifications, so you may assume that it is configured in the same way as the official image, except for the added plugin and database lookup utility. It also has the countries&rsquo; database built in, but you can always override it with your own database using the Docker volume mounting features.</p> <p>You can build this project by yourself or just use a pre-built image in order to run an already configured NGINX instance based on <code>nginx-alpine</code> image:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">docker run --rm -p 80:80 bubelov/nginx-alpine-geoip2 </span></span></code></pre></div><p>Check the log output now. You should see the country info at the end of your log records:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">183.88.21.120 - - [16/Apr/2019:09:08:55 +0000] &#34;GET / HTTP/1.1&#34; </span></span><span class="line"><span class="cl">200 612 &#34;-&#34; </span></span><span class="line"><span class="cl">&#34;Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:66.0) Gecko/20100101 Firefox/66.0&#34; &#34;-&#34; </span></span><span class="line"><span class="cl">TH Thailand </span></span></code></pre></div><h2 id="alternative">Alternative</h2> <p>It is also possible to enrich NGINX logs with geo data at a latter stage. We may let NGINX write logs as is in its default format and then process them line by line, mapping IP addresses to geolocations. I really like <a href="https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-module-nginx.html">Filebeat Nginx module</a> which utilizes such an approach, and it works out of the box with Elastic stack.</p> <h2 id="conclusion">Conclusion</h2> <p>Now we have a NGINX server that can identify the geolocation of its clients. This information is stored in the NGINX&rsquo;s <code>access.log</code> and can be processed by your metrics or log analysis tools of choice such as Logstash, Telegraf and so on. You can also get more precise locations by plugging in the cities&rsquo; database by following the instructions provided in <a href="https://github.com/leev/ngx_http_geoip2_module">ngx_http_geoip2_module</a> repository.</p> The End of Power https://bubelov.com/blog/2019/end-of-power/ Tue, 09 Apr 2019 00:00:00 +0000 https://bubelov.com/blog/2019/end-of-power/ <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#preface">Preface</a></li> <li><a href="#what-is-power">What is Power?</a></li> <li><a href="#power-and-technology">Power and Technology</a></li> <li><a href="#centralization-in-business">Centralization in Business</a></li> <li><a href="#what-about-governments">What About Governments?</a></li> <li><a href="#shifts-in-power">Shifts in Power</a></li> <li><a href="#risks-of-the-new-paradigm">Risks of the New Paradigm</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="preface">Preface</h2> <p>Power is a broad, slippery term. It&rsquo;s hard to define, let alone debate meaningfully. That&rsquo;s exactly why Moisés Naím&rsquo;s book, The End of Power, is so compelling. Naím knows the subject firsthand: he served as a government minister and later as an executive director of the World Bank.</p> <h2 id="what-is-power">What is Power?</h2> <p>There is no agreement on what power is exactly, even among the people who dedicate their lives researching it. Many great thinkers have their own views on the nature of power and on why most of the people seem to pursue it with such a passion. My favorite take on power was made by Thomas Hobbes in Leviathan:</p> <blockquote> <p>The power of a man is his present means, to obtain some future apparent good. &hellip; So that in the first place, I put for a general inclination of all mankind, a perpetual and restless desire of power after power, that ceaseth only in death. And the cause of this, is not always that a man hopes for a more intensive delight, than he has already attained to; or that he cannot be content with a moderate power: but because he cannot assure the power and means to live well, which he hath present, without the acquisition of more.</p> <p>― <a href="https://en.wikipedia.org/wiki/Thomas_Hobbes">Thomas Hobbes</a> in <a href="https://en.wikipedia.org/wiki/Leviathan_(Hobbes_book)">Leviathan</a></p> </blockquote> <p>This is a nice definition, but it&rsquo;s too abstract to have any meaningful discussion. Moisés Naím assumes a more simple and formal definition of power in order to make things clear:</p> <blockquote> <p>Power is the ability to direct or prevent the current or future actions of other groups and individuals. Or, put differently, power is what we exercise over others that leads them to behave in ways they would not otherwise have behaved.</p> <p>― <a href="https://www.moisesnaim.com/">Moisés Naím</a> in <a href="https://en.wikipedia.org/wiki/The_End_of_Power">The End of Power</a></p> </blockquote> <h2 id="power-and-technology">Power and Technology</h2> <p>One of the main ideas of this book is that power closely follows technology. Centralization was a main theme of the previous century, and it made a lot of sense to scale up many of our systems from manufacturing to politics. Everything had an epic and extreme scale: factories, wars, political alliances and so on. We enjoyed many benefits of centralization such as cheaper costs of production, but we also suffered from world wars and extreme ideologies that wanted nothing less than to take over the world by any means necessary.</p> <h2 id="centralization-in-business">Centralization in Business</h2> <p>The interesting thing is that in the 21st century, scale becomes more of a drawback than a boon. Do we really want to have one point of failure? Let&rsquo;s take &ldquo;too big to fail&rdquo; companies as an example. Are we comfortable bailing them out in case they make a mistake, just for the sake of stability? It feels unfair for many people because it is unfair but that&rsquo;s the main outcome of centralization: people become dependent on a monopolist who promises to be &ldquo;good&rdquo; but has no interest in keeping that promise.</p> <h2 id="what-about-governments">What About Governments?</h2> <p>In theory, democratic governments reflect the will of the people, but many are not democratic. That means billions live under a centralized power serving other interests at their expense.</p> <p>In my view, this is just traditional US propaganda. There&rsquo;s no real difference between &ldquo;democracies&rdquo; and &ldquo;non‑democracies&rdquo;, it&rsquo;s a false dichotomy. There&rsquo;s no such thing as a permanently unpopular government since truly unpopular ones are quickly replaced by more capable alternatives. So I&rsquo;d argue every country is equally democratic (or, more accurately, equally undemocratic). If you&rsquo;re interested in a deeper argument, I recommend reading Leviathan.</p> <p>To add a more cynical layer: all governments are ultimately run in the interest of a ruling class. Once you see that, appeals to &ldquo;democratic values&rdquo; can only make you smile. This is a classical critique of state power, most famously associated with Marxist analysis but shared in various forms by anarchists and political realists like Machiavelli long before.</p> <h2 id="shifts-in-power">Shifts in Power</h2> <p>The author&rsquo;s argument goes like this: The centralized political and business structures of the past are losing power to smaller players and automated tools. People are becoming less dependent on these institutions, reaching incredible levels of self‑sovereignty, but it comes at a cost.</p> <p>Centralized governance can be effective for moving a nation forward. Less centralized systems often struggle to reach a consensus. Without a clear authority to make decisive choices, the whole system can grind to a halt. A recent example of this struggle is the Brexit process: a controversial, multi‑party effort where various veto powers simply blocked meaningful progress.</p> <p>I&rsquo;m not sold on this framing of the Brexit example. Using it to argue against decentralization feels politically loaded. The gridlock might say more about the specific, flawed design of that process than about decentralization itself.</p> <h2 id="risks-of-the-new-paradigm">Risks of the New Paradigm</h2> <p>There are many potential threats that come from the diffusion of power, but the author puts an extra accent on the following five risks:</p> <ul> <li> <p>Disorder: abolishment of universal rules may bring chaos and anarchy.</p> </li> <li> <p>Loss of Knowledge: smaller organizations may lack tradition as well as an incentive to invest into large and expensive R&amp;D projects.</p> </li> <li> <p>The Banalization of Social Movements: many of our society&rsquo;s biggest problems require high-risk and high-scale orchestration and the diffusion of power makes it hard to unite for a common cause or drags people into pointless internal fights.</p> </li> <li> <p>Boosting Impatience: everyone is a broadcaster now, which sometimes shifts our attention from working on our long term goals to obsessing with the short term and emotionally charged issues. Short-terminism and emotions are the exact opposite of the Enlightenment values of rationality and the rule of law.</p> </li> <li> <p>Alienation: trust is a foundation of any successful society, we cannot achieve any progress without some basic level of trust between each other. The diffusion of power leads to the diffusion of trust. Most of the failed states of our days have a low level of trust between citizens, and the most successful countries also score high on the level of mutual trust which makes the deterioration of trust a very concerning development.</p> </li> </ul> <h2 id="conclusion">Conclusion</h2> <p>The End of Power is a great book that summarizes the recent shifts in our society, and our relationships with power. In my opinion, those trends won&rsquo;t stop anytime soon, and we will see more radical diffusion of power in the future, but it&rsquo;s still unknown how far it can go. I believe that this process will have a net positive effect on a society, but it won&rsquo;t be easy for us to re-adjust for the new reality and there are still many uncertainties that we have to be concerned about because they may greatly affect our lives.</p> The Yield Curve Has Inverted https://bubelov.com/blog/2019/yield-curve-inverted/ Wed, 27 Mar 2019 00:00:00 +0000 https://bubelov.com/blog/2019/yield-curve-inverted/ <p>The yield curve is an important thing to watch for if you are trying to understand what’s going to happen with the economy. Yield curve inversion often means we’re about a year from the next recession. Let’s dig a bit deeper in order to understand how it works and what can we do about it.</p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#what-is-yield-curve">What Is Yield Curve?</a></li> <li><a href="#what-about-now">What About Now?</a></li> <li><a href="#what-moves-the-yield-curve">What Moves The Yield Curve?</a></li> <li><a href="#what-can-we-do-about-it">What Can We Do About It?</a></li> <li><a href="#conclusion">Conclusion</a></li> <li><a href="#appendix">Appendix</a></li> </ul> </nav> </div> <h2 id="what-is-yield-curve">What Is Yield Curve?</h2> <p>The yield curve shows the relation between the interest rate and the term of the bonds. We’re going to talk about US Treasury bonds only, for simplicity&rsquo;s sake. The x-axis shows the term and the y-axis shows the return. Here is how the yield curve looks most of the time:</p> <p><figure> <a href="https://bubelov.com/blog/2019/yield-curve-inverted/yield-curve-2018-10-25_hu_e424b25285de4c1f.webp"> <img src="https://bubelov.com/blog/2019/yield-curve-inverted/yield-curve-2018-10-25_hu_cd4216e41f5458d5.webp" alt="" /> </a> <figcaption>Yield curve for 2018-10-25</figcaption> </figure></p> <p>What can it tell us? Nothing interesting, actually. We may conclude that the investors expect higher return for buying the longer term bonds, which is completely rational. There are more things that can go wrong with the economy in the next 30 years than in the next 3 months, so the investors usually want some kind of compensation for taking the extra risk of buying the longer term bonds. There are several alternative explanations that extend or even contradict this model, but the important thing to get from this chart is that it is the “normal” shape of the yield curve. This shape has the same form most of the time, at least since the Great Depression.</p> <h2 id="what-about-now">What About Now?</h2> <p>How does the yield curve look now? Not very well, actually. As you may see, it changed its shape, and it has become partly inverted:</p> <p><figure> <a href="https://bubelov.com/blog/2019/yield-curve-inverted/yield-curve-2019-03-22_hu_117a23ff1edccd50.webp"> <img src="https://bubelov.com/blog/2019/yield-curve-inverted/yield-curve-2019-03-22_hu_a21875aa033bd836.webp" alt="" /> </a> <figcaption>Yield curve for 2019-03-22</figcaption> </figure></p> <p>So, what’s happening here? The longer term bonds still look normal, but the short term bond yields look like a mess. In other words, it means that, for certain terms, the investors are willing to pay more to get less. You can get 2.49% return on 1 month bonds, but you can only get 2.44% return on 10 year bonds!</p> <h2 id="what-moves-the-yield-curve">What Moves The Yield Curve?</h2> <p>Every segment of the yield curve can be affected by supply and demand so, if more people suddenly decide to buy bonds with a specific term, the yield on that term will decrease. An increase in demand also increases the current price, which automatically decreases the future yield for any bond with the same term.</p> <p>So the current yield curve actually tells us that the demand for medium term bonds (3, 5, and 10 years to maturity) have increased significantly despite their decreased profitability compared to the shorter term bonds. To make an analogy with retail banking: why would anyone reject a 1-year deposit with a higher return and buy a 5-year deposit with a lower return? It would be more wise to reopen a 1-year deposit 5 years in a row, but only if the deposit rate would stay the same and this is the core of the problem with the current state of the yield curve. It seems like the market expects the rates to go down in the next few years, and the investors just want to “lock-in” the current rates for the next few years or so by buying more medium term bonds.</p> <p>OK, but what does this have to do with the recession? The problem is, the interest rates do not go down when everything is great. Lowered interest rates are a classic response to economic shocks, so the market thinks that we’re going to experience something horrible really soon. Can we rely on this assumption? Not completely, there is nothing “for sure” in the economy, but an inverted yield curve has predicted most of the recessions in the past decades, so it’s a very strong indicator.</p> <h2 id="what-can-we-do-about-it">What Can We Do About It?</h2> <p>Yield curve inversion is a good reminder that things will not always be better in the near future, sometimes things get much worse. Every recession is full of personal dramas: a lot of people lose their jobs, their debts become unmanageable, their stock market investments lose a lot of value and all of that occurring at the same time is not fun at all. Generally, we should always be prepared for financial difficulties, but now we have one more reason to make sure we will be able to navigate potential hardships without taking too much damage. It makes sense to boost our savings, reduce our debts and think more about what we’re going to do if the future turns out to be worse than we expect in the times of economic growth.</p> <h2 id="conclusion">Conclusion</h2> <p>It’s extremely hard to predict future recessions, and I doubt that anyone can do that. All we have is a bunch of indicators such as the yield curve that have done a good job at predicting the past recessions. Of course, past events do not guarantee that it’s going to be the same in the future but as we found out, the yield curves are not a bunch of magic numbers - it’s a good indicator of market sentiment and if investors are preparing for the worst, maybe everyone else should at least make sure that the next recession will not catch them by surprise.</p> <h2 id="appendix">Appendix</h2> <p>Here is how the yield curve evolved since December 2018:</p> <p><figure> <a href="https://bubelov.com/blog/2019/yield-curve-inverted/yield-curve.gif"> <img src="https://bubelov.com/blog/2019/yield-curve-inverted/yield-curve.gif" alt="" /> </a> <figcaption>Yield curve from Sep 2018 to Mar 2019</figcaption> </figure></p> The Future of Payments https://bubelov.com/blog/2019/future-of-payments/ Sat, 02 Mar 2019 00:00:00 +0000 https://bubelov.com/blog/2019/future-of-payments/ <p><figure> <a href="https://bubelov.com/blog/2019/future-of-payments/thumb_hu_e9c90a7cce0148ff.webp"> <img src="https://bubelov.com/blog/2019/future-of-payments/thumb_hu_3989f1112948a1c6.webp" alt="" /> </a> </figure></p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#preface">Preface</a></li> <li><a href="#payment-cards-hide-their-true-cost">Payment Cards Hide Their True Cost</a></li> <li><a href="#payment-cards-are-surveillance-devices">Payment Cards Are Surveillance Devices</a></li> <li><a href="#payment-cards-add-counterparty-risk">Payment Cards Add Counterparty Risk</a></li> <li><a href="#is-there-a-better-way-to-pay">Is There a Better Way to Pay?</a></li> <li><a href="#regulations">Regulations</a></li> <li><a href="#bitcoin">Bitcoin</a></li> <li><a href="#lightning-network">Lightning Network</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="preface">Preface</h2> <p>Transactions are the glue of society. For centuries, we&rsquo;ve experimented with ways to store wealth and exchange value, constantly pushing to lower costs and speed things up. Today, we can buy almost anything instantly, often with no fees. So, the problem is solved, right?</p> <h2 id="payment-cards-hide-their-true-cost">Payment Cards Hide Their True Cost</h2> <p>You might not notice the difference between paying cash and paying with a card because the true cost of card payments is hidden from customers. Merchants pay a fee for every card transaction they accept, and they pass that cost on to you by raising prices. Between 0.2% and 3.0% of what you spend ends up going to banks and payment processors, not counting the other fees they charge you more directly. In the end, you&rsquo;re the one paying for it.</p> <h2 id="payment-cards-are-surveillance-devices">Payment Cards Are Surveillance Devices</h2> <p>Privacy matters. It&rsquo;s no surprise it&rsquo;s a hot topic, just think about the <a href="https://www.nytimes.com/2018/03/19/technology/facebook-cambridge-analytica-explained.html">Facebook‑Cambridge Analytica scandal</a>. People are becoming more aware of their digital footprints and want to control the data that companies, including banks and payment processors, silently collect and analyze. Although people and governments are pushing for more transparency and accountability, it&rsquo;s still profitable to harvest and sell user data, so most data‑hoarding companies do exactly that.</p> <p>Granted, data brokers can&rsquo;t get away with everything. Regulations like the <a href="https://eugdpr.org/">GDPR</a> are helping people protect their privacy. But those rules aren&rsquo;t universal, and they&rsquo;re far from perfect. Corporate greed always finds a way to bend the law for profit.</p> <p>Even well‑meaning companies are a privacy risk: they still have to store data somewhere, and that data will always be a honeypot for hackers.</p> <h2 id="payment-cards-add-counterparty-risk">Payment Cards Add Counterparty Risk</h2> <p>We think of the money in our checking accounts as &ldquo;ours&rdquo;, but do we really control it? Technically, the bank takes your money and promises to give it back when you ask. But banks can&rsquo;t always keep that promise.</p> <p>Even US banks have collapsed during crises, and the US has the world&rsquo;s most stable economy. Thinking it can&rsquo;t happen again is naive. The risk is far worse in many developing countries.</p> <p>Take Russia. The government guarantees up to $25,000 per person if a bank fails, a ridiculously low amount. Business accounts aren&rsquo;t protected at all. Keeping cash safe in a bank is practically impossible.</p> <h2 id="is-there-a-better-way-to-pay">Is There a Better Way to Pay?</h2> <p>I believe these problems can be tackled with a two‑pronged approach:</p> <ul> <li><strong>Regulations</strong> to set clear rules and guardrails.</li> <li><strong>Solving trust and privacy at a fundamental level</strong> with Bitcoin.</li> </ul> <h2 id="regulations">Regulations</h2> <p>Good regulations can drastically cut the cost of payments. One of my favorite examples is <a href="https://transferwise.com/">TransferWise</a>. Thanks to regulatory shifts, they were able to compete with big banks and dramatically lower the cost of international transfers and currency exchange. We need more of this: making it easier for agile fintech firms to challenge slow, inefficient banks, which will drive down costs and improve convenience for everyone.</p> <p>We also need rules that stop banks from collecting more data than is strictly necessary for KYC/AML compliance, and ideally force them to delete most historical data after a short period.</p> <p>Counterparty risk can be reduced with smart regulation, too. Standards like <a href="https://www.bis.org/bcbs/basel3.htm">Basel III</a> aim to curb the reckless behavior that can put consumers&rsquo; money at risk. These kinds of frameworks are ultimately good for users.</p> <h2 id="bitcoin">Bitcoin</h2> <p>Bitcoin has gained a lot of traction recently, and its properties are worth a look. It already competes with traditional payment methods on cost in some markets, international transfers, for example, can be cheaper and faster with Bitcoin.</p> <p>But paying for a cup of coffee with it? Neither easy nor convenient. Trust me, I&rsquo;ve tried.</p> <p><strong>What about data privacy?</strong> This one&rsquo;s tricky. Bitcoin transactions are public, which can be even more revealing than a bank statement. The silver lining is that the data isn&rsquo;t directly tied to your identity, but with enough effort, someone can track your activity on the blockchain. So, yes, Bitcoin still has a way to go on this front.</p> <p><strong>And counterparty risk?</strong> Practically none. That&rsquo;s Bitcoin&rsquo;s killer feature today: you don&rsquo;t have to trust anyone. No one can stop you from using your own money. In a world full of middlemen, that&rsquo;s a huge step forward for individual control and cutting out counterparty risk.</p> <h2 id="lightning-network">Lightning Network</h2> <p>Lightning Network is a payment system built on top of Bitcoin, and I believe it has a huge potential for many kinds of payments. Here you can see it in action:</p> <video controls> <source src="lightning-payment.webm" type="video/webm"> </video> <p>Not too bad, right? There is room for improvement in terms of the interface, and I would like to see it supporting NFC enabled devices in order to simplify the payment process, but the underlying network works, and it works fast. The fees are negligible, and, unlike Bitcoin blockchain transactions, Lightning transactions are anonymous, so you don&rsquo;t have to worry about the privacy of your payment history.</p> <h2 id="conclusion">Conclusion</h2> <p>Although our current payment methods are easy and relatively cheap, there are many issues with the status quo. We need to address them by introducing better regulations and experimenting with alternative payment methods such as Bitcoin and Lightning Network. Reduced transaction costs, increased privacy and reduced counterparty risks will benefit us all, so the progress in those areas seems inevitable.</p> Coroutines in Kotlin https://bubelov.com/blog/2019/kotlin-coroutines/ Fri, 01 Feb 2019 00:00:00 +0000 https://bubelov.com/blog/2019/kotlin-coroutines/ <p>Coroutines have been around since Kotlin 1.1, but Kotlin 1.3 was the first release to include them as a <strong>stable API</strong>, meaning they&rsquo;re finally ready for production. They’re a great tool for writing and maintaining asynchronous code without losing your mind.</p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#what-are-coroutines">What are Coroutines?</a></li> <li><a href="#coroutines-in-action">Coroutines in Action</a></li> <li><a href="#benefits-of-coroutines">Benefits of Coroutines</a></li> <li><a href="#costs-of-coroutines">Costs of Coroutines</a></li> <li><a href="#anatomy-of-coroutines-context">Anatomy of Coroutines: Context</a></li> <li><a href="#anatomy-of-coroutines-dispatchers">Anatomy of Coroutines: Dispatchers</a></li> <li><a href="#anatomy-of-coroutines-launchers">Anatomy of Coroutines: Launchers</a></li> <li><a href="#anatomy-of-coroutines-jobs">Anatomy of Coroutines: Jobs</a></li> <li><a href="#anatomy-of-coroutines-async-builders-and-deferred-results">Anatomy of Coroutines: Async Builders and Deferred Results</a></li> <li><a href="#anatomy-of-coroutines-scopes-and-strucrured-concurrency">Anatomy of Coroutines: Scopes and Strucrured Concurrency</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="what-are-coroutines">What are Coroutines?</h2> <p>Any seasoned programmer knows that <a href="https://www2.eecs.berkeley.edu/Pubs/TechRpts/2006/EECS-2006-1.pdf">threads are evil</a>. You should think twice, maybe three times, before reaching for them. Threads are powerful, but they come at a terrible cost: code that&rsquo;s error‑prone and a pain to maintain.</p> <p>Coroutines let you write concurrent code without explicitly managing threads. Their main goal is to keep the benefits of concurrency while slashing the costs, especially by making the code more readable and almost indistinguishable from plain, sequential Kotlin.</p> <p>In Kotlin, the terms &ldquo;coroutines&rdquo; and &ldquo;suspending functions&rdquo; are often used interchangeably. In fact, <strong>suspending functions are the real &ldquo;magic&rdquo; behind coroutines.</strong></p> <h2 id="coroutines-in-action">Coroutines in Action</h2> <p>Theory is important, but it&rsquo;s nice to see a practical example before committing your time to learn something new. Maybe this fancy tool isn&rsquo;t a good fit for your particular tasks? Let&rsquo;s look at the following function:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">fun</span> <span class="nf">displayBooks</span><span class="p">()</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">progressBar</span><span class="p">.</span><span class="n">show</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> <span class="n">loadBooks</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> <span class="n">progressBar</span><span class="p">.</span><span class="n">hide</span><span class="p">()</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>If you&rsquo;re familiar with UI programming, it&rsquo;s very easy to see the problem with this code. The <code>loadBooks()</code> function can take an unpredictable amount of time, and most UI toolkits are single-threaded, so the UI thread will get blocked. Chances are, we won&rsquo;t even see that progress bar. Here is the flowchart which shows what this code really does:</p> <p><figure> <a href="https://bubelov.com/blog/2019/kotlin-coroutines/sync_hu_7e859aec4ab2a8e3.webp"> <img src="https://bubelov.com/blog/2019/kotlin-coroutines/sync_hu_dbb73ba35a0ffb96.webp" alt="" /> </a> </figure></p> <p>The &ldquo;load data&rdquo; stage blocks the UI and makes it unresponsive. It means that users won&rsquo;t see any animations and that they won&rsquo;t be able to press any buttons or interact with anything UI related. The thread is busy, nothing can be done until it completes all three of those steps. That&rsquo;s not what we want and there must be a way to do it right.</p> <p>Well, that&rsquo;s where threads shine. We can execute <code>loadBooks()</code> in a separate thread in order to unblock the UI thread and make our program responsive:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">fun</span> <span class="nf">displayBooks</span><span class="p">()</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">progressBar</span><span class="p">.</span><span class="n">show</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">thread</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">loadBooks</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">progressBar</span><span class="p">.</span><span class="n">hide</span><span class="p">()</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>It&rsquo;s a bit better, but it still won&rsquo;t have the desired effect. The problem is: there is no guarantee that <code>progressBar.hide()</code> will be called after <code>loadBooks()</code>. The extra thread that we introduced is now messing with the execution order. Let&rsquo;s look at this chart:</p> <p><figure> <a href="https://bubelov.com/blog/2019/kotlin-coroutines/new-thread_hu_f6f61544ae2d3c.webp"> <img src="https://bubelov.com/blog/2019/kotlin-coroutines/new-thread_hu_bcaa28039c9f84b3.webp" alt="" /> </a> </figure></p> <p>When we read the code top to bottom, it looks like it should complete all the steps in the order they were declared:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="n">progressBar</span><span class="p">.</span><span class="n">show</span><span class="p">()</span> </span></span><span class="line"><span class="cl"><span class="n">loadBooks</span><span class="p">()</span> </span></span><span class="line"><span class="cl"><span class="n">progressBar</span><span class="p">.</span><span class="n">hide</span><span class="p">()</span> </span></span></code></pre></div><p>In fact, the program will to this:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="n">progressBar</span><span class="p">.</span><span class="n">show</span><span class="p">()</span> </span></span><span class="line"><span class="cl"><span class="n">progressBar</span><span class="p">.</span><span class="n">hide</span><span class="p">()</span> </span></span><span class="line"><span class="cl"><span class="n">loadBooks</span><span class="p">()</span> </span></span></code></pre></div><p>As anything thread-related, it looks like a trivial task but ends up being a huge pain in the ass. We tend to read the code like a book, and the intuitive way to interpret it is to assume that it always completes in a strict top-to-bottom order. Spawning threads introduces tremendous mental burden and becomes a constant source of different issues which are notoriously hard to debug.</p> <p>It looks like &ldquo;just spawn a new thread&rdquo; solution is flawed so what are our options? Many traditional solutions rely on sending messages between the &ldquo;worker&rdquo; and UI threads, and they tend to complicate the code quite a bit. They also make it hard to handle different errors and edge cases.</p> <p>If only there was a way to keep the code simple and avoid messing with the execution order. As you may have guessed, that&rsquo;s exactly what coroutines are for. Let&rsquo;s look at the following code:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">suspend</span> <span class="k">fun</span> <span class="nf">displayBooks</span><span class="p">()</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">progressBar</span><span class="p">.</span><span class="n">show</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">withContext</span><span class="p">(</span><span class="nc">Dispatchers</span><span class="p">.</span><span class="n">IO</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">loadBooks</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">progressBar</span><span class="p">.</span><span class="n">hide</span><span class="p">()</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>It looks similar to the previous example but let&rsquo;s look at the order of execution:</p> <p><figure> <a href="https://bubelov.com/blog/2019/kotlin-coroutines/coroutine_hu_67f2512d7b2d1278.webp"> <img src="https://bubelov.com/blog/2019/kotlin-coroutines/coroutine_hu_ee9ce7fe1184bc54.webp" alt="" /> </a> </figure></p> <p>As you can see, the UI thread is not getting blocked while the data loads, but it also doesn&rsquo;t proceed to the next line until the data is ready. This ability to wait without blocking the thread is called &ldquo;suspension&rdquo;. If you look at the function definition, you will see the <code>suspend</code> keyword was added before the <code>fun</code> keyboard. That&rsquo;s how we let Kotlin know that we want a particular function to be able to suspend its own execution and to free its thread for other tasks, when necessary.</p> <p>In other words, coroutine is just a function that can be partially executed. Since coroutines are not that different from normal functions, you can convert any function into a coroutine just by adding <code>suspend</code> before the <code>fun</code> keyword.</p> <h2 id="benefits-of-coroutines">Benefits of Coroutines</h2> <p>One of the main benefits of coroutines is their readability, they look similar to regular functions, but they are much more powerful. Code readability tends to be the victim when it comes to concurrency because Java concurrency APIs are complex and error prone and the alternatives such as RxJava can also complicate the code and mess with its structure.</p> <p>Another important fact about coroutines is that they are lightweight. You can spawn hundreds of thousands of coroutines on your laptop, and it won&rsquo;t crash your system. Threads, on the contrary, are expensive, and you should keep a close look on how many threads you program uses.</p> <h2 id="costs-of-coroutines">Costs of Coroutines</h2> <p>Coroutines don&rsquo;t change the fact that you should not use concurrency unless you really need it. Here are the biggest cons of using coroutines in your code:</p> <ul> <li> <p>Coroutines and Kotlin are different things. There are lots of good programmers who are perfectly comfortable with Kotlin but know nothing about coroutines. Coroutines require a programmer to grasp a few new concepts such as suspending functions, jobs, dispatchers and structured concurrency. Some advanced and more complicated use cases also require a good understanding of hot/cold flows, and many patterns that were built upon them. Let&rsquo;s be clear: all of that is a cost, and that cost is pretty high.</p> </li> <li> <p>Badly implemented coroutines can break your programs in many ways, some of them aren&rsquo;t that obvious and might take a lot of time to detect. Avoid concurrency unless it&rsquo;s absolutely necessary.</p> </li> <li> <p>Coroutines are harder to debug and debugging the code that uses coroutines is different from debugging vanilla Kotlin. One more thing to learn and support so make sure you really need it.</p> </li> </ul> <h2 id="anatomy-of-coroutines-context">Anatomy of Coroutines: Context</h2> <p>Coroutine context is just a set of <a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines.experimental/-coroutine-context/-element/index.html">CoroutineContext.Element</a> instances. Every coroutine should have a context, and it&rsquo;s usually created automatically when you launch a coroutine using one of the popular coroutine builders. Let&rsquo;s take <a href="https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html">CoroutineScope.launch()</a> as an example:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">public</span> <span class="k">fun</span> <span class="nf">CoroutineScope</span><span class="p">.</span><span class="n">launch</span><span class="p">(</span> </span></span><span class="line"><span class="cl"> <span class="n">context</span><span class="p">:</span> <span class="n">CoroutineContext</span> <span class="p">=</span> <span class="n">EmptyCoroutineContext</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">start</span><span class="p">:</span> <span class="n">CoroutineStart</span> <span class="p">=</span> <span class="nc">CoroutineStart</span><span class="p">.</span><span class="n">DEFAULT</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="n">block</span><span class="p">:</span> <span class="k">suspend</span> <span class="nc">CoroutineScope</span><span class="p">.()</span> <span class="o">-&gt;</span> <span class="n">Unit</span> </span></span><span class="line"><span class="cl"><span class="p">):</span> <span class="n">Job</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">newContext</span> <span class="p">=</span> <span class="n">newCoroutineContext</span><span class="p">(</span><span class="n">context</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">coroutine</span> <span class="p">=</span> <span class="k">if</span> <span class="p">(</span><span class="n">start</span><span class="p">.</span><span class="n">isLazy</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">LazyStandaloneCoroutine</span><span class="p">(</span><span class="n">newContext</span><span class="p">,</span> <span class="n">block</span><span class="p">)</span> <span class="k">else</span> </span></span><span class="line"><span class="cl"> <span class="n">StandaloneCoroutine</span><span class="p">(</span><span class="n">newContext</span><span class="p">,</span> <span class="n">active</span> <span class="p">=</span> <span class="k">true</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">coroutine</span><span class="p">.</span><span class="n">start</span><span class="p">(</span><span class="n">start</span><span class="p">,</span> <span class="n">coroutine</span><span class="p">,</span> <span class="n">block</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="n">coroutine</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>There are 4 different keys values which can be stored in <code>CoroutineContext</code>:</p> <ul> <li><code>CoroutineName</code> - used to name a coroutine, mostly for debugging purposes</li> <li><code>Job</code> - a cancellable thing, can have a parent job as well as children jobs. Jobs are hierarchical, and the cancellation of children jobs does not lead to cancellation of their parent jobs</li> <li><code>ContinuationInterceptor</code> - <code>CoroutineContext</code> instance that intercepts coroutine continuations</li> <li><code>CoroutineExceptionHandler</code> - an optional element used to handle uncaught exceptions</li> </ul> <h2 id="anatomy-of-coroutines-dispatchers">Anatomy of Coroutines: Dispatchers</h2> <p>Coroutine dispatchers determine what threads their coroutines should use for their execution. Imagine the situation when you have to interact with some UI elements, and you really need to access them from the UI thread. You can create a special CoroutineDispatcher for UI interactions, but it&rsquo;s usually provided by the coroutines library.</p> <p>There are 4 predefined dispatchers that should be enough for most of the apps:</p> <ul> <li> <p><code>Default</code>: That&rsquo;s what coroutines use by default. This dispatcher has a pool of threads which is equal to the number of CPU cores available to your program so this dispatcher can run coroutines in parallel. One of the good uses for this dispatcher is to run any CPU intensive calculations. This way, we can split our coroutines evenly between all the CPU cores available.</p> </li> <li> <p><code>IO</code>: The main purpose of this dispatcher is to handle IO tasks such us reading from or writing into files or performing network requests. This dispatcher uses a pool of threads, so it&rsquo;s also capable of parallel execution but this pool can be substantially bigger than the pool used by <strong>Default</strong> dispatcher.</p> </li> <li> <p><code>Unconfined</code>: This dispatcher is not confined to any thread which makes it unpredictable, and you should avoid using it unless you have a strong reason to do otherwise.</p> </li> <li> <p><code>Main</code>: The implementation of this dispatcher varies from platform to platform, and it is always confined to the UI thread. It&rsquo;s probably a good idea to execute all of your UI interacting pieces of coroutines with this dispatcher.</p> </li> </ul> <h2 id="anatomy-of-coroutines-launchers">Anatomy of Coroutines: Launchers</h2> <p>Here is the most popular ways to start a coroutine:</p> <ul> <li><code>CoroutineScope.launch()</code> - launches a coroutine in a non-blocking way</li> <li><code>runBlocking()</code> - launches a coroutine and block until it finishes. This function is useful in unit tests when you want to do all the assertions before the test function returns</li> </ul> <p>Once started, your coroutine is free to call other coroutines directly.</p> <h2 id="anatomy-of-coroutines-jobs">Anatomy of Coroutines: Jobs</h2> <p>Each coroutine implements Job interface:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Coroutine</span> <span class="p">:</span> <span class="n">Job</span> </span></span></code></pre></div><p>Job is a cancellable thing with a life-cycle that culminates in its completion. You can always check the state of your jobs by reading its <code>isActive</code>, <code>isCompleted</code> or <code>isCancelled</code> properties.</p> <p>Probably the most important methods of Job interface are:</p> <ul> <li><code>cancel()</code> - cancels the job, and it&rsquo;s children</li> <li><code>join()</code> - blocks until this job is complete</li> </ul> <h2 id="anatomy-of-coroutines-async-builders-and-deferred-results">Anatomy of Coroutines: Async Builders and Deferred Results</h2> <p>There is a special coroutine builder called <code>CoroutineScope.async()</code> which lets you call another coroutine in a non-blocking way. This function gives you a <code>Deferred</code> result which means that it might not yet be available. You can just call the <code>await()</code> method at some point in the future, so it&rsquo;s pretty easy to execute several coroutines in parallel and then just call the <code>await()</code> on them to get the results.</p> <h2 id="anatomy-of-coroutines-scopes-and-strucrured-concurrency">Anatomy of Coroutines: Scopes and Strucrured Concurrency</h2> <p>Lifecycle management is a core part of software development. Apps are often built from many components, and each can have its own lifecycle, separate from the rest of the app.</p> <p>Take an Android screen that shows data fetched from a server. The user opens it, sees a loading indicator, and then&hellip; they might wait, or they might get bored and leave before the data arrives. If they leave, we need a way to shut down every coroutine that was fetching that data.</p> <p>That&rsquo;s exactly what <strong>coroutine scopes</strong> are for.</p> <p>Every coroutine builder is an extension of CoroutineScope, so the only thing you have to do is to define a special scope for each component that has a separate lifecycle. Let&rsquo;s take the Android&rsquo;s ViewModel as an example:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">AuthViewModel</span><span class="p">(</span><span class="n">authRepository</span><span class="p">:</span> <span class="n">AuthRepository</span><span class="p">)</span> <span class="p">:</span> <span class="n">ViewModel</span><span class="p">()</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">private</span> <span class="k">val</span> <span class="py">job</span> <span class="p">=</span> <span class="n">Job</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> <span class="k">private</span> <span class="k">val</span> <span class="py">uiScope</span> <span class="p">=</span> <span class="n">CoroutineScope</span><span class="p">(</span><span class="nc">Dispatchers</span><span class="p">.</span><span class="n">Main</span> <span class="p">+</span> <span class="n">job</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">private</span> <span class="k">val</span> <span class="py">authResult</span> <span class="p">=</span> <span class="n">MutableLiveData</span><span class="p">&lt;</span><span class="n">AuthResult</span><span class="p">&gt;()</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">override</span> <span class="k">fun</span> <span class="nf">onCleared</span><span class="p">()</span> <span class="p">=</span> <span class="n">job</span><span class="p">.</span><span class="n">cancel</span><span class="p">()</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">fun</span> <span class="nf">signIn</span><span class="p">(</span><span class="n">email</span><span class="p">:</span> <span class="n">String</span><span class="p">,</span> <span class="n">password</span><span class="p">:</span> <span class="n">String</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">uiScope</span><span class="p">.</span><span class="n">launch</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">result</span> <span class="p">=</span> <span class="n">authRepository</span><span class="p">.</span><span class="n">signIn</span><span class="p">(</span><span class="n">email</span><span class="p">,</span> <span class="n">password</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">authResult</span><span class="p">.</span><span class="k">value</span> <span class="p">=</span> <span class="n">result</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>As you can see, this component has its own scope which uses <code>Dispatchers.Main</code> as a default dispatcher. We can easily stop doing what we&rsquo;re doing once we receive the <code>onCleared()</code> callback from the external component that manages ViewModels on Android.</p> <h2 id="conclusion">Conclusion</h2> <p>Coroutines is a powerful addition to the Kotlin language, and they allow us to write complex multithreaded logic which doesn&rsquo;t look very different from the code we usually write for single threaded use. Coroutines readability is superior to any of the popular alternatives, and I think that coroutines will become more popular among Kotlin developers in the future.</p> Cost of Living in Phuket https://bubelov.com/blog/2019/cost-of-living-in-phulet/ Sun, 27 Jan 2019 00:00:00 +0000 https://bubelov.com/blog/2019/cost-of-living-in-phulet/ <p><figure> <a href="https://bubelov.com/blog/2019/cost-of-living-in-phulet/header_hu_8cb31885cb8dd206.webp"> <img src="https://bubelov.com/blog/2019/cost-of-living-in-phulet/header_hu_38eec7e65c4c05c3.webp" alt="" /> </a> </figure></p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#preface">Preface</a></li> <li><a href="#why-phuket">Why Phuket?</a></li> <li><a href="#currency">Currency</a></li> <li><a href="#accommodation">Accommodation</a></li> <li><a href="#utilities">Utilities</a></li> <li><a href="#food--clothing">Food &amp; Clothing</a></li> <li><a href="#entertainment">Entertainment</a></li> <li><a href="#additional-expenses">Additional Expenses</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="preface">Preface</h2> <p>Thailand has been a top choice for remote workers and retirees over the past decade, thanks to its warm weather, affordable prices, and welcoming culture. The cost of living comes up all the time in conversations with friends and colleagues who are thinking about visiting, or even staying a few months, in Phuket.</p> <h2 id="why-phuket">Why Phuket?</h2> <p>Thailand isn&rsquo;t cheap compared to the neighboring countries and Phuket is the most expensive province in Thailand. In my opinion, the benefits of infrastructure outweigh the extra costs. It never hurts to have a high-speed Internet connection or being able to visit well-equipped hospital if you feel sick. The roads are decent, too.</p> <h2 id="currency">Currency</h2> <p>The official currency in Thailand is Thai Baht (THB), and it&rsquo;s surprisingly stable. 1 USD can be exchanged for ~32 THB, and it&rsquo;s not hard to find an exchange booth with a fair rate after your arrival, just don&rsquo;t do it in the airport, it&rsquo;s usually a ripoff.</p> <h2 id="accommodation">Accommodation</h2> <p>The cheapest apartments start from 4,000 THB ($130) per a month. Living in a room like that will be a torture, since they usually don&rsquo;t have an air-con. A decent condo with a good location, security, air-con, parking and a swimming pool would cost you about 8,000 THB ($260) and this is a nice choice for a single person, but it&rsquo;s not the best choice for a couple. Here is the condo that I rented for a few months:</p> <p><figure> <a href="https://bubelov.com/blog/2019/cost-of-living-in-phulet/condo_hu_45077565d2bbdaa8.webp"> <img src="https://bubelov.com/blog/2019/cost-of-living-in-phulet/condo_hu_d325c786ebbec778.webp" alt="" /> </a> <figcaption>Condo in Phuket</figcaption> </figure></p> <p>This condo costs about 9,000 THB ($285) a month and it&rsquo;s a good choice if you&rsquo;re planning to live alone. The bigger options would be a two-bedroom condo or a detached house, and they would cost you 10,000-15,000 THB ($320-$475).</p> <h2 id="utilities">Utilities</h2> <p>The electricity bill for a condo should be under 1,000 THB ($32), assuming you have an air-con, and you use it pretty often. The water bill should be below 100 THB ($3), and the internet bill is usually below 500 THB ($16).</p> <h2 id="food--clothing">Food &amp; Clothing</h2> <p>Your food expenses will largely depend on what kind of food you prefer. The european food is expensive in Thailand, probably because it&rsquo;s imported so there is no way you would pay less for it than in the EU, the import costs are high, and your wine drinking habit might cost you a lot. Here are the typical prices for some European foods:</p> <ul> <li>Wine: 500+ THB ($16+) per bottle</li> <li>Cheese: 1,000+ THB ($32+) per kilo</li> <li>Decent beer: 150 THB ($5) per bottle</li> </ul> <p>Clothing prices may vary, but they are more or less the same for the international brands.</p> <p>The locally produced food is much cheaper, and you can stay within 4,000 THB ($130) a month if you don&rsquo;t buy a lot of imported foods and about 7,000 THB ($222) a month should be enough for a person who buys all kinds of food from time to time.</p> <h2 id="entertainment">Entertainment</h2> <p>The cafes and restaurants in Phuket are cheaper compared to the western countries. You can have dinner for two in a fancy place for about 1,000 THB ($32). Movie tickets are not that different from developed countries, and it&rsquo;s safe to expect about 350 THB ($11) for a ticket in the evenings. The national parks have a habit of charging extra money for foreigners so be ready to pay up to 350 THB ($11) for a visit.</p> <h2 id="additional-expenses">Additional Expenses</h2> <p>All foreigners need a visa in order to stay in Thailand long term and that can cost you quite a bit. The actual numbers may vary depending on your length of stay, and your visa type but on average it costs about 3,500 THB ($111) a month.</p> <p>Another thing to consider is transportation. There is no public transport in Phuket, so you usually have 2 options:</p> <ul> <li>Rent a bike: 3,500+ THB ($111+) a month</li> <li>Rent a car: 10,000+ THB ($317+) a month (and 1000+ THB ($32+) for gasoline)</li> </ul> <p>Oh, and we probably have to include health insurance in our estimation. The actual price may depend on your age but 2,000 THB ($63) a month would be a fair guess.</p> <h2 id="conclusion">Conclusion</h2> <p>Let&rsquo;s sum up all the categories above to come up with an estimate cost of living in Phuket:</p> <ul> <li>Condo: 8,000 THB ($253)</li> <li>Utilities: 1,500 THB ($48)</li> <li>Food &amp; clothing: 7,000 THB ($222)</li> <li>Entertainment: 2,000 THB ($63)</li> <li>Visa expenses: 3,500 THB ($111)</li> <li>Bike rental: 3,500 THB ($111)</li> <li>Health insurance: 2,000 THB ($63)</li> </ul> <p>Total expenses: <strong>27,500 THB ($870)</strong></p> <p>As you can see, it&rsquo;s not expensive to live in Phuket, but it is also not cheap. The cost of living would be close to <strong>40,000 THB ($1,270)</strong> if you prefer to rent a car and if you want to live in a more spacious condo or a house.</p> <p>Personally, I like to use <a href="https://www.numbeo.com/cost-of-living/compare_cities.jsp?country1=Canada&amp;city1=Toronto&amp;country2=Thailand&amp;city2=Phuket&amp;displayCurrency=USD">NUMBEO</a> when I want to quickly compare two different cities in terms of costs of living and more often than not, those estimates tend to be pretty accurate.</p> ATOM RPG https://bubelov.com/blog/2019/atom-rpg/ Thu, 24 Jan 2019 00:00:00 +0000 https://bubelov.com/blog/2019/atom-rpg/ <p><figure> <a href="https://bubelov.com/blog/2019/atom-rpg/thumb_hu_336c6536346572b7.webp"> <img src="https://bubelov.com/blog/2019/atom-rpg/thumb_hu_b9c23459162ac2ed.webp" alt="" /> </a> </figure></p> <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#preface">Preface</a></li> <li><a href="#graphics">Graphics</a></li> <li><a href="#gameplay">Gameplay</a></li> <li><a href="#role-system">Role System</a></li> <li><a href="#what-makes-this-game-unique">What Makes This Game Unique?</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="preface">Preface</h2> <p>The trailers made it obvious: ATOM RPG is an indie clone of the early Fallout games. What&rsquo;s interesting is that the developers didn&rsquo;t even try to hide it, they leaned into it, even poking fun at some Fallout cliches. As a big fan of the series, I decided to give it a shot. I wasn&rsquo;t disappointed.</p> <h2 id="graphics">Graphics</h2> <p>The first two Fallout games are ancient. I doubt you could even run them on a modern PC without an emulator, and even if you managed, the original graphics would look pretty rough since we&rsquo;re all used to higher resolutions and better visuals now.</p> <p>I&rsquo;m glad ATOM RPG didn&rsquo;t try to clone the original Fallout pixel‑for‑pixel. Instead, the developers went with modern 3D graphics. Here&rsquo;s a screenshot from the main menu:</p> <p><figure> <a href="https://bubelov.com/blog/2019/atom-rpg/main-menu_hu_184235d95d97e92f.webp"> <img src="https://bubelov.com/blog/2019/atom-rpg/main-menu_hu_ee5945fbd4283f7.webp" alt="" /> </a> <figcaption>Main menu</figcaption> </figure></p> <p>It looks pretty good, especially for an indie game.</p> <h2 id="gameplay">Gameplay</h2> <p>The gameplay is very close to the early Fallout games, and I think that&rsquo;s intentional. Why change what works perfectly? There&rsquo;s a huge debate over whether Fallout 3 and Fallout 4 are &ldquo;true&rdquo; Fallouts, and it&rsquo;s a valid one.</p> <p>Most game designers see themselves as artists. You can&rsquo;t exactly tell an artist, &ldquo;make an exact clone of this masterpiece, and don&rsquo;t add anything new, it&rsquo;ll only make it worse&rdquo;. Artists have their own ideas. When working on sequels to classic games, they often try to express themselves, which regularly disappoints longtime fans.</p> <p>I like Fallout 4, but I also admit it has almost nothing in common with Fallout and Fallout 2 in terms of gameplay. Until now, we haven&rsquo;t had a 3D game that truly replicates the old 2D Fallout experience. ATOM RPG finally brings 3D graphics to that classic style, and it doesn&rsquo;t feel like a completely different game, it feels like a natural extension of the original Fallout world.</p> <h2 id="role-system">Role System</h2> <p>The role system is very close to Fallout&rsquo;s original SPECIAL system. You can also choose starting &ldquo;prior conditions&rdquo; when creating a character, which makes each playthrough and character build feel unique.</p> <p>The skill system works the same as in Fallout and Fallout 2. You earn skill points each level and distribute them among a wide range of abilities, from pistol handling and speech craft to survival and science.</p> <p><figure> <a href="https://bubelov.com/blog/2019/atom-rpg/skills_hu_a9a3fb09eaeb427c.webp"> <img src="https://bubelov.com/blog/2019/atom-rpg/skills_hu_9f5b83e87e481c1.webp" alt="" /> </a> <figcaption>Skills window</figcaption> </figure></p> <h2 id="what-makes-this-game-unique">What Makes This Game Unique?</h2> <p>Despite its many similarities to the old Fallout games, ATOM RPG has plenty of its own flavor. The developers come from ex‑USSR countries, and the game is set in a post‑nuclear Soviet Union. That changes the entire aesthetic: buildings, cars, and NPCs look completely different from what you&rsquo;d see in a Fallout title.</p> <p>There are also tons of cultural references that might fly over the head of someone unfamiliar with Soviet and post‑Soviet life. Take the random encounters with scavengers on the world map, for example:</p> <p><figure> <a href="https://bubelov.com/blog/2019/atom-rpg/gopniks_hu_fb316b66c75bc41f.webp"> <img src="https://bubelov.com/blog/2019/atom-rpg/gopniks_hu_f7228f1393acc782.webp" alt="" /> </a> <figcaption>Dangerous encounter</figcaption> </figure></p> <p>The scene shows squatting men, one in a newsboy cap. That might not mean anything to a western or Asian player, but anyone from an ex‑USSR country knows these are &ldquo;gopniks&rdquo;, a low‑life social group that survives on petty crime.</p> <p>All these details make ATOM RPG feel like a <strong>Slavic Fallout</strong>. The Slavic vibe might seem odd to western fans, but I don&rsquo;t see that as a bad thing. A 100% clone would be boring. This game feels more like 75% Fallout mixed with 25% weird, original ingredients that fit surprisingly well into the classic gameplay formula.</p> <h2 id="conclusion">Conclusion</h2> <p>ATOM RPG is a great game that takes the best parts of the early Fallout titles and brings them into 3D. It&rsquo;s also packed with the same kind of dark‑humor‑filled dialogue that made Fallout so memorable.</p> <p>I&rsquo;ve put over 50 hours into it and really enjoyed the ride. I was also impressed that the developers kept polishing the game after launch, it just keeps getting better. Honestly, I have no idea how a team that size pulled off a project on this scale. ATOM RPG doesn&rsquo;t feel like a low‑budget indie effort, and I was really hoping they&rsquo;d make a sequel.</p> <p><strong>Update:</strong> <a href="https://bubelov.com/blog/2021/trudograd/">They did</a>.</p> Phishing for Phools https://bubelov.com/blog/2018/phishing-for-phools/ Fri, 28 Dec 2018 00:00:00 +0000 https://bubelov.com/blog/2018/phishing-for-phools/ <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#preface">Preface</a></li> <li><a href="#profit-seeking-and-morality">Profit Seeking and Morality</a></li> <li><a href="#irrational-behavior">Irrational Behavior</a></li> <li><a href="#should-we-enforce-rationality">Should We Enforce Rationality?</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="preface">Preface</h2> <p>This book is pretty much what you&rsquo;d expect from a behavioral economist. It&rsquo;s full of entertaining stories that track the age‑old conflict between opportunistic entrepreneurs and the governments trying to shield us from our own predictable mistakes.</p> <h2 id="profit-seeking-and-morality">Profit Seeking and Morality</h2> <p>The book&rsquo;s core idea is simple: humans aren&rsquo;t 100% rational, and everyone has weak spots that can be exploited by those chasing profit without regard for the suffering they cause. The authors aren&rsquo;t against profit itself, they acknowledge many honest businesspeople with good intentions. But they also stress there are plenty who&rsquo;ll do anything for a gain, no matter the human cost.</p> <p>Personally, I think markets and morality are two different things, and maybe it&rsquo;s better if they stay that way. Businesspeople aren&rsquo;t philosophers. They can be great at spotting and filling demand, but they can be completely delusional about everything else.</p> <p>Take drug cartels. I don&rsquo;t think buying or selling drugs is inherently immoral, but killing people in the process definitely is. So who&rsquo;s to blame? Should we fault business for discovering and satisfying this demand? Should we blame consumers for creating it? Or should we blame governments for outlawing it, which handed the market to the most ruthless criminals?</p> <p>Morality is complex. Profit-seeking, at its base, is simple.</p> <h2 id="irrational-behavior">Irrational Behavior</h2> <p>One of the most interesting stories in Phishing for Phools is about the gambling industry. Gambling was far more widespread in the past, and countless people lost everything to addiction. Is gambling rational in a cold utilitarian sense? Of course not, people have known that for thousands of years, yet they still do it.</p> <p>Gambling addiction takes many forms: lotteries, casinos, slot machines in a corner store. People will bet on anything: sports, chicken fights, <a href="https://bubelov.com/blog/2018/crypto-as-asset-class/">crypto</a>. The authors credit governments for outlawing many gambling activities, which has helped shrink the pool of addicts. Thanks to those regulations, plenty of potential gamblers never lost their money in the first place.</p> <h2 id="should-we-enforce-rationality">Should We Enforce Rationality?</h2> <p>I think those stories are great, and they make a lot of sense. It&rsquo;s hard to deny the positive effects of regulations. Gambling is prohibited in most of the countries, which prevents a lot of suffering. Anti tobacco campaigns have saved many lives but, unfortunately, it&rsquo;s not the whole story. The war on drugs, for example, was lost and there is a lot of evidence that it only made things worse.</p> <p>I think it&rsquo;s very dangerous to make decisions for other people, and it&rsquo;s tempting for any government to say &ldquo;I know better!&rdquo; and ban something. It&rsquo;s disturbing that the governments treat people as idiots who do not know what they really want. I don&rsquo;t doubt the benefits of certain restrictions, but the negative effects of centralization of power and control can greatly outweigh those benefits. History shows us that idiots can reach pretty high places in politics and force their will on others.</p> <p>Another issue I have with such a solution is the moral one: should we make the lives of some people harder in order to make the lives of other people easier? Let&rsquo;s take taxes on alcohol as an example: alcohol is not bad in moderation so why are we taxing it more than bananas? The regulation proponents would say it&rsquo;s because some people can&rsquo;t drink in moderation. Yea, that&rsquo;s a good point, but why should the rest of us pay for that?</p> <h2 id="conclusion">Conclusion</h2> <p>Phishing for Phools is a great book that makes a familiar behavioral‑economics point: we&rsquo;re not as rational as we think. It&rsquo;s packed with compelling stories about profiting from deception and how governments try to counter those &ldquo;immoral&rdquo; practices.</p> <p>Behavioral economists are often brilliant at weaving anecdotes into a good narrative, I just wish they were as productive at building credible financial models.</p> <p>Personally, I don&rsquo;t see regulation as a one‑size‑fits‑all fix, and I think we should be cautious about unnecessary government intervention. The recent push to restrict free speech is a telling example. It&rsquo;s tempting to label those who disagree with us as either evil or stupid, but reality is more complicated. More rules and censorship often backfire. Paternalism tends to create unintended consequences, and it&rsquo;s surprisingly easy to end up doing more harm than good.</p> Stand Firm: Resisting The Self-Improvement Craze https://bubelov.com/blog/2018/stand-firm/ Mon, 17 Dec 2018 00:00:00 +0000 https://bubelov.com/blog/2018/stand-firm/ <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#preface">Preface</a></li> <li><a href="#cut-out-the-navel-gazing">Cut Out The Navel-Gazing</a></li> <li><a href="#focus-on-the-negative">Focus on The Negative</a></li> <li><a href="#suppress-your-feelings">Suppress Your Feelings</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="preface">Preface</h2> <p>This book has a rather provocative title, probably that&rsquo;s why it grabbed my attention, and I decided to buy it to see what does the author actually mean by &ldquo;Self-Improvement Craze&rdquo;. I mean, who doesn&rsquo;t want to improve himself?</p> <p>As it turned out, this book has nothing against self-improvement per se. Instead, it questions the most notorious ideas of various self-improvement movements. Those ideas may look appealing if you don&rsquo;t pay enough attention to their consequences, but they can do more harm than good.</p> <p>The author tries to &ldquo;invert&rdquo; many popular mottoes of the self-improvement movements to show us that the opposite of those ideas may have much more sense. Here are a few examples I found interesting:</p> <h2 id="cut-out-the-navel-gazing">Cut Out The Navel-Gazing</h2> <p>One of the distinctive features of our time is everyone&rsquo;s obsession with the idea of self. People are constantly trying to find out whom they really are and to reach the highest level of self realization. That&rsquo;s an admirable effort but what makes us so sure that the answer is inside us? Maybe it is the case that self-analysis can be harmful?</p> <p>The author mentions the health paradox as an example: more medical attention often makes us feel worse. Getting medical help feeds our obsession with self-diagnosis which leads us to false conclusions about our health. Is it possible that &ldquo;search of the true self&rdquo; can be as dangerous as receiving unnecessary medical treatment?</p> <h2 id="focus-on-the-negative">Focus on The Negative</h2> <p>What&rsquo;s worse than death? Some try to imagine it: one guy argued it&rsquo;s far worse to die while your enemies still live. Beyond these clever thought experiments, we can safely say death is the ultimate negative: inevitable, universally dreaded, and an event we avoid by any means and try not to think about.</p> <p>Should we ever think about such a negative event? The author sides with the stoics in thinking that the focus on the negative events is not necessarily a bad thing. Thinking about the things we may lose can help us to set our priorities straight and to appreciate things that we have now because we might lose them at any moment.</p> <h2 id="suppress-your-feelings">Suppress Your Feelings</h2> <p>The author makes a good point in writing that people that are always positive may seem insincere. Another problem is the idea that we should always express our feelings. Self-help gurus like to use children as an example of &ldquo;proper&rdquo; attitude to life. Children tend to express their emotions freely, be positive and optimistic about the future, and they are usually a good example of &ldquo;living in a moment&rdquo;. There are many issues with that:</p> <ol> <li>No one likes the adults who behave like the bad-tempered babies. It seems like people are becoming more infantile, thanks to the bad advice of self-help gurus.</li> <li>Our feelings are not who we are. They just come and go, there is nothing wrong with suppressing our feelings.</li> <li>Looking forward is great, but there are a lot of things we can learn from looking back.</li> </ol> <h2 id="conclusion">Conclusion</h2> <p>This book offers a good introduction into the stoic philosophy as a tool that helps people to stay sane in this crazy and ever-changing world. I would definitely recommend this book to anyone who feels stressed by the pressure to keep up with everything in life or feels the pressure to conform to any of those popular new ideas of the self-help gurus.</p> Kotlin Contracts https://bubelov.com/blog/2018/kotlin-contracts/ Mon, 03 Dec 2018 00:00:00 +0000 https://bubelov.com/blog/2018/kotlin-contracts/ <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#preface">Preface</a></li> <li><a href="#example">Example</a></li> <li><a href="#solution">Solution</a></li> <li><a href="#another-example">Another Example</a></li> <li><a href="#solution-1">Solution</a></li> <li><a href="#realworld-usage">Real‑World Usage</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="preface">Preface</h2> <p>The Kotlin compiler is pretty smart about static analysis, but there are still times when code looks perfectly obvious to a programmer, but is very hard for the compiler to figure out. Contracts were introduced in Kotlin 1.3 to solve exactly that. You can use them to tell the compiler your intent in situations where it can&rsquo;t be sure, clearing up ambiguity so it can do its job.</p> <h2 id="example">Example</h2> <p>Consider this code:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">fun</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">signIn</span><span class="p">(</span><span class="s2">&#34;qwerty&#34;</span><span class="p">)</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">fun</span> <span class="nf">signIn</span><span class="p">(</span><span class="n">password</span><span class="p">:</span> <span class="n">String</span><span class="p">?)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="n">validatePassword</span><span class="p">(</span><span class="n">password</span><span class="p">))</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">println</span><span class="p">(</span><span class="s2">&#34;Password starts with </span><span class="si">${password[0]}</span><span class="s2">&#34;</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="c1">// Sign in </span></span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">fun</span> <span class="nf">validatePassword</span><span class="p">(</span><span class="n">password</span><span class="p">:</span> <span class="n">String</span><span class="p">?):</span> <span class="n">Boolean</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="n">password</span> <span class="o">!=</span> <span class="k">null</span> <span class="o">&amp;&amp;</span> <span class="n">password</span><span class="p">.</span><span class="n">length</span> <span class="p">&gt;</span> <span class="mi">0</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>This code looks right to a programmer, but the Kotlin compiler will throw an error. The reason is: Kotlin can&rsquo;t track nullability checks across function boundaries. So even if <code>password</code> was validated in a called function, the compiler still sees it as nullable here.</p> <h2 id="solution">Solution</h2> <p>Since Kotlin 1.3, we can explicitly define the relationship between a function&rsquo;s inputs and its outcome so the compiler is confident enough to perform a smart cast. Let&rsquo;s modify our example to do that:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">fun</span> <span class="nf">validatePassword</span><span class="p">(</span><span class="n">password</span><span class="p">:</span> <span class="n">String</span><span class="p">?):</span> <span class="n">Boolean</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">contract</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">returns</span><span class="p">(</span><span class="k">true</span><span class="p">)</span> <span class="n">implies</span> <span class="p">(</span><span class="n">password</span> <span class="o">!=</span> <span class="k">null</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="n">password</span> <span class="o">!=</span> <span class="k">null</span> <span class="o">&amp;&amp;</span> <span class="n">password</span><span class="p">.</span><span class="n">length</span> <span class="p">&gt;</span> <span class="mi">0</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>Now the compiler has a little more information about what we&rsquo;re doing. Because we called <code>validatePassword</code> and it returned <code>true</code>, the compiler won&rsquo;t complain about <code>password</code> being nullable here.</p> <h2 id="another-example">Another Example</h2> <p>In this example, we&rsquo;re going to try to initialize a value inside a block of code:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">fun</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">val</span> <span class="py">result</span><span class="p">:</span> <span class="n">Int</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">executeCode</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">result</span> <span class="p">=</span> <span class="mi">1</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">println</span><span class="p">(</span><span class="n">result</span><span class="p">)</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">inline</span> <span class="k">fun</span> <span class="nf">executeCode</span><span class="p">(</span><span class="n">block</span><span class="p">:</span> <span class="p">()</span> <span class="o">-&gt;</span> <span class="n">Unit</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">block</span><span class="p">()</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>This code looks innocent to a programmer, but the Kotlin compiler won&rsquo;t run it. Let&rsquo;s see what it says:</p> <ul> <li><em>Captured values initialization is forbidden due to possible reassignment</em></li> <li><em>Variable <code>result</code> must be initialized</em></li> </ul> <h2 id="solution-1">Solution</h2> <p>Luckily, there&rsquo;s a special type of contract that lets us tell the compiler exactly how many times a given block of code will be called. Let&rsquo;s modify our <code>executeCode</code> method:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">inline</span> <span class="k">fun</span> <span class="nf">executeCode</span><span class="p">(</span><span class="n">block</span><span class="p">:</span> <span class="p">()</span> <span class="o">-&gt;</span> <span class="n">Unit</span><span class="p">)</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">contract</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="n">callsInPlace</span><span class="p">(</span><span class="n">block</span><span class="p">,</span> <span class="nc">InvocationKind</span><span class="p">.</span><span class="n">EXACTLY_ONCE</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">block</span><span class="p">()</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>Now we can compile and run our code without getting any errors.</p> <h2 id="realworld-usage">Real‑World Usage</h2> <p>Contracts are already used in the Kotlin Standard Library. Here are a few examples:</p> <ul> <li>The <code>synchronized</code> method</li> <li>Precondition functions (<code>require</code>, <code>check</code>, etc.)</li> <li>Standard‑Library scope functions (<code>also</code>, <code>apply</code>, <code>let</code>, etc.)</li> <li>String utilities <code>isNullOrBlank</code> and <code>isNullOrEmpty</code> (so strings can be smart‑cast as non‑null if these return <code>false</code>)</li> <li>The collection utility <code>isNullOrEmpty</code></li> <li><code>Result</code> class extensions</li> </ul> <h2 id="conclusion">Conclusion</h2> <p>Contracts can make your code more readable, but use them carefully because the compiler won&rsquo;t verify they&rsquo;re correct, it just trusts you on that. They&rsquo;re especially useful at the library level, but they can also be handy in application code when you need to help the compiler understand your intent.</p> Cryptocurrencies as an Asset Class https://bubelov.com/blog/2018/crypto-as-asset-class/ Mon, 05 Nov 2018 00:00:00 +0000 https://bubelov.com/blog/2018/crypto-as-asset-class/ <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#preface">Preface</a></li> <li><a href="#origins-of-bitcoin">Origins of Bitcoin</a></li> <li><a href="#what-about-other-coins">What About Other Coins?</a></li> <li><a href="#should-i-invest-in-bitcoin">Should I Invest in Bitcoin?</a></li> <li><a href="#investing-for-money">Investing for Money</a></li> <li><a href="#investing-for-status">Investing for Status</a></li> <li><a href="#investing-for-fun">Investing for Fun</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="preface">Preface</h2> <p>The world&rsquo;s first cryptocurrency, Bitcoin, just turned 10. In that time, its price shot up from almost nothing to a staggering $20,000 before cooling off to around $6,500 today. A ride like that was never going to be quiet, the drama and controversy were inevitable.</p> <h2 id="origins-of-bitcoin">Origins of Bitcoin</h2> <p>Some people think the whole story started with the Bitcoin <a href="bitcoin.pdf">white paper</a>, the document that outlined the system&rsquo;s high-level design. A working prototype followed shortly after, as open‑source software anyone with a PC could run. You don&rsquo;t even need a conventional internet connection to join the Bitcoin network, you can use a satellite dish or any other data‑link to reach other nodes.</p> <p>I think understanding Bitcoin&rsquo;s origins helps us see its original purpose and future potential. Unfortunately, no one knows for sure where it came from. Even the real name of its creator (or creators) is a complete mystery.</p> <p>There are plenty of wild theories: some accuse it of being a CIA pet project, others argue it was backed by drug cartels, its sole purpose to make life easier for criminals.</p> <p>That&rsquo;s not a lot to go on. But here&rsquo;s the thing about open‑source software: <strong>it isn&rsquo;t static</strong>. Most of the people directing the project today are public figures, you can look up their backgrounds. I won&rsquo;t name names, but here are the common traits I&rsquo;ve seen among developers who&rsquo;ve worked on Bitcoin from the early days:</p> <ul> <li><strong>They aren&rsquo;t filthy rich</strong>, which often surprises outsiders.</li> <li><strong>They are highly competent</strong> and take pride in that.</li> <li><strong>They value their privacy</strong> far more than the average person.</li> <li>Many of them are <strong>cypherpunks</strong> (<a href="https://www.activism.net/cypherpunk/manifesto.html">here’s a quick overview of cypherpunk ideas</a>).</li> </ul> <p>If you dig a little deeper, it&rsquo;s clear Bitcoin wasn&rsquo;t the first attempt to create a digital currency or &ldquo;digital gold&rdquo;, but all the earlier ones failed. It&rsquo;s important to understand that <strong>Bitcoin didn&rsquo;t come out of nowhere</strong>. Its design is actually an attempt to fix the problems that killed its predecessors.</p> <h2 id="what-about-other-coins">What About Other Coins?</h2> <p>The early days are long gone. Now there are thousands of crypto assets, and the biggest coins and tokens are usually listed on <a href="https://coinmarketcap.com/">Coinmarketcap</a>.</p> <p>Almost all of them inherited a big chunk of Bitcoin&rsquo;s DNA, but each project is unique in some way. This brings us to one of the most important properties of crypto assets: <strong>their price movements are highly correlated</strong>, leaving very little room for actual diversification.</p> <p>Some might argue every coin is fundamentally different, but the market doesn&rsquo;t seem to agree. Don&rsquo;t fool yourself into thinking you can significantly cut your investment risk just by spreading your money across a few coins or tokens. Bitcoin is by far the most stable crypto asset and adding other assets will likely increase the risk in your portfolio.</p> <p>Believe it or not, some crypto assets have made even more money than Bitcoin, though they&rsquo;ve also been more volatile. So, buying a few altcoins can make sense. Just watch out for con artists: there are thousands of Ponzi schemes masquerading as promising crypto projects.</p> <h2 id="should-i-invest-in-bitcoin">Should I Invest in Bitcoin?</h2> <p>I think it mostly depends on why you&rsquo;re investing. There are three widely known motivations that drive investment activity:</p> <ul> <li><strong>Money</strong></li> <li><strong>Status</strong></li> <li><strong>Fun</strong></li> </ul> <h2 id="investing-for-money">Investing for Money</h2> <p>Does it make sense to invest in Bitcoin or other crypto assets from a financial perspective? I&rsquo;d say yes. It&rsquo;s a rare case where the upside is practically limitless, while the downside is capped, you can&rsquo;t lose more than you put in.</p> <p>The best most companies can hope for is to become a monopoly, but even then they can&rsquo;t extract unlimited value. Demand is finite, and competition is always ready to move in and drive profits down.</p> <p>Bitcoin, however, can theoretically have unlimited value growth, it could even become the definition of value. There&rsquo;s a hard limit on the number of bitcoins, but no limit on the goods and services our society produces. That output has grown at about 3% a year since the Industrial Revolution. So, <strong>it&rsquo;s not unreasonable to expect an unlimited growth trajectory for any widely adopted, scarce store of value</strong>.</p> <p>Satoshi Nakamoto, the mysterious creator(s) of Bitcoin, expected it to become &ldquo;huge&rdquo; or vanish completely. There&rsquo;s a lot of space between those extremes, though. Personally, I lean toward that &ldquo;all or nothing&rdquo; view, but it&rsquo;s also possible it ends up as a niche product, finds a stable price, and hits a growth ceiling down the road.</p> <h2 id="investing-for-status">Investing for Status</h2> <p>Will Bitcoin satisfy an investor looking for social status? I&rsquo;m not so sure. A lot of people in crypto don&rsquo;t like to talk about this. I guess there&rsquo;s a social stigma, and it&rsquo;s not hard to see why. How many can prove they got their crypto legitimately? Then there&rsquo;s plain envy. If your investment does well, a lot of people will call you a lucky gambler. If you lose money, they&rsquo;ll think you were naive or stupid. Either way, negativity seems hard to avoid.</p> <p>On the other hand, plenty of people genuinely support the crypto movement and its core ideas. Still, let&rsquo;s just say the <strong>status impact of crypto investing is, at the very least, controversial</strong>.</p> <h2 id="investing-for-fun">Investing for Fun</h2> <p>I think <strong>fun is the biggest reason to invest in cryptocurrencies</strong>. If you&rsquo;re hooked on adrenaline and drama, this is the perfect community to dive into. The crypto market is the digital wild west, packed with all sorts of characters, each with their own agenda.</p> <p>Sure, there are plenty of honest people, but even they have their egos tied up in the debate. And then there&rsquo;s a whole legion of con artists and criminals who thrive in this space. Your job is to figure out who&rsquo;s who and put your money where your mouth is. One thing you&rsquo;re guaranteed to get if you invest in crypto is a good rollercoaster ride.</p> <h2 id="conclusion">Conclusion</h2> <p>Crypto assets can be a good option for almost any investor, but they&rsquo;re especially suited for fun‑loving types with high risk tolerance. While they can bring huge returns, they&rsquo;re also far more volatile than most stocks or bonds.</p> <p>Because of that extreme volatility, I think most investors shouldn&rsquo;t have a large crypto allocation. The exact percentage depends on many factors, but aiming for <strong>1% to 10% of your portfolio</strong> in crypto assets could be a sensible long‑term strategy.</p> Mastering Bitcoin https://bubelov.com/blog/2015/mastering-bitcoin/ Thu, 15 Oct 2015 00:00:00 +0000 https://bubelov.com/blog/2015/mastering-bitcoin/ <div class="table-of-contents"> <h2>Table of Contents</h2> <nav id="TableOfContents"> <ul> <li><a href="#preface">Preface</a></li> <li><a href="#what-is-money">What is Money?</a></li> <li><a href="#money-as-a-software">Money as a Software</a></li> <li><a href="#blockchain">Blockchain</a></li> <li><a href="#new-risks">New Risks</a></li> <li><a href="#being-in-control">Being in Control</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> </nav> </div> <h2 id="preface">Preface</h2> <p>I just finished reading Mastering Bitcoin by <a href="https://aantonop.com/">Andreas M. Antonopoulos</a>. It does an excellent job of explaining the technical side of Bitcoin, the world&rsquo;s most popular decentralized digital currency, and unpacking what those technical terms actually mean.</p> <p>The book is fairly technical, assuming a basic grasp of math, programming, and cryptography, but it also delves into the philosophical and political issues surrounding Bitcoin. It&rsquo;s hard to find anyone who hasn&rsquo;t heard of Bitcoin, yet very few can explain what it actually is. People often say it&rsquo;s &ldquo;a kind of money&rdquo;, but what do we really know about money? What&rsquo;s the difference between your average national currency and Bitcoin?</p> <h2 id="what-is-money">What is Money?</h2> <p>Every modern national currency tries to do three main jobs:</p> <ul> <li>Be a medium of exchange (you can buy stuff with it).</li> <li>Be a store of value (it won&rsquo;t vanish overnight).</li> <li>Be a unit of account (it&rsquo;s how we set prices and track value).</li> </ul> <p>That&rsquo;s the core of the commodity theory of money. If you&rsquo;re up for some philosophy, check out this <a href="https://plato.stanford.edu/entries/money-finance/#WhatMone">great article</a> from Stanford for a deeper look at what money actually is and how it works, or at least, how we think it works.</p> <p>Bitcoin has all of those &ldquo;commodity&rdquo; properties, but it also has a lot of new and unique features. In my view, that leaves us no choice but to recognize decentralized currencies as a completely new asset class.</p> <p>Are bitcoins just like national currencies? Obviously not, all national currencies are centralized and have unpredictable supplies.</p> <p>We could try to think of bitcoins as shares in a company, but there&rsquo;s no company issuing them or giving you any claim on future profits. So bitcoins aren&rsquo;t stocks or bonds, and there isn&rsquo;t really an existing <a href="https://bubelov.com/blog/2018/crypto-as-asset-class/">asset class</a> where decentralized currencies fit neatly.</p> <h2 id="money-as-a-software">Money as a Software</h2> <p>The days of physical money are long gone. Most of today&rsquo;s money supply exists in a purely digital form, with no peg to gold or any other physical commodity. The same is true for Bitcoin.</p> <p>People understand traditional money intuitively. A central bank &ldquo;prints&rdquo; it, the banking system distributes it, and everyone trusts the basic deal: work gets you money, and money gets you what you need.</p> <p>Here&rsquo;s the difference: Bitcoin is just a computer program, an algorithm that refuses to take orders from any central authority. It&rsquo;s hard to believe that&rsquo;s possible without diving into cryptography, but the fact that Bitcoin hasn&rsquo;t vanished tells us this isn&rsquo;t science fiction.</p> <p>In that sense, Bitcoin is closer to gold than to national currencies. You simply can&rsquo;t print more of it, even if you&rsquo;re a central banker.</p> <p>This book is for developers and tech entrepreneurs who want to understand Bitcoin on a technical level. The main point is that Bitcoin isn&rsquo;t just money. Sure, you can use it as money, but that&rsquo;s like using a laptop only to display pictures.</p> <p>The Bitcoin network can execute a set of instructions. People can combine them to create powerful new features, many of which haven&rsquo;t even been discovered yet.</p> <h2 id="blockchain">Blockchain</h2> <p>Mr. Antonopoulos emphasizes that the real innovation isn&rsquo;t Bitcoin as a currency, but the blockchain technology that underpins it. The blockchain is the core tool that makes Bitcoin possible, and Bitcoin is just the first widely adopted application of this technology.</p> <p>Strictly speaking, <strong>blockchain and Bitcoin are inseparable</strong>, and there&rsquo;s no point trying to figure out which part is more important. What matters is that together they create a new framework, enabling a whole new class of services to be built.</p> <p>A blockchain is a special kind of database that can store almost anything. The trick is that it&rsquo;s unforgeable: once data is recorded, it stays there forever. Anyone can read it or add new information, but <strong>it&rsquo;s impossible to alter what&rsquo;s already there</strong>.</p> <p>The blockchain used by Bitcoin records financial transactions, but it&rsquo;s not just about account balances. Think about credit history: most companies track it in some way, and for good reason. It helps build trust, and <strong>trust is one of the most valuable assets in any society</strong>.</p> <p>Wouldn&rsquo;t it be useful if a company could instantly verify how trustworthy a potential customer is? Having the ability to present provable facts could improve society, creating stronger incentives to honor contracts and discouraging bad behavior.</p> <p>This idea is a bit controversial, of course. Sharing your entire transaction history with everyone is risky. While Bitcoin doesn&rsquo;t fully solve this privacy challenge, I like the idea of selective sharing, maybe as a one‑time proof for a specific purpose. I think that approach could have a future.</p> <h2 id="new-risks">New Risks</h2> <p>The author argues it&rsquo;s not just the tech that matters. Bitcoin has created a whole set of philosophical and ethical issues we need to resolve. What even is money? How should it work? Should it be anonymous? <strong>Is less national sovereignty a good thing?</strong> We&rsquo;ll face these hard questions more and more as Bitcoin grows.</p> <p>Do the benefits outweigh the risks? One of Bitcoin&rsquo;s earliest uses was an online drug marketplace. Drugs are legal in some places, but in others, selling or using them can get you executed. Bitcoin is a truly global currency, so it can&rsquo;t be regulated at the national level. The same goes for capital controls. Bitcoin simply <strong>can&rsquo;t enforce local regulations</strong>, and it was designed that way on purpose.</p> <p>It&rsquo;s no surprise that for some, the first impression of Bitcoin wasn&rsquo;t great. It was seen as a tool for illegal activities like drug trade and tax evasion. But that perception has started to shift.</p> <p>Today, you&rsquo;ll find blockchain research teams and Bitcoin‑specific courses at the world&rsquo;s top universities. Banks have started putting crypto research into their R&amp;D budgets. It&rsquo;s become pretty easy to land a job at a major bank if you understand cryptocurrencies, but banks now face stiff competition from hundreds of startups using Bitcoin&rsquo;s payment network to create cheap alternatives to traditional finance.</p> <h2 id="being-in-control">Being in Control</h2> <p>Another area where Bitcoin shows its value is in risk management. People are now very dependent on credit cards. What happens if you lose your card on vacation or during a business trip? What if it&rsquo;s stolen? That can leave you in a tough spot, withdrawing cash can be difficult, especially when you&rsquo;re outside your bank&rsquo;s home country.</p> <p>We can also imagine a situation where someone needs to make a payment, but their bank refuses to process it, whether due to a technical error or an account suspension. With Bitcoin, that&rsquo;s no longer a problem. There are over 450 Bitcoin ATMs worldwide, and chances are any major city you visit already has one. So if you&rsquo;ve lost your card, or for any other reason can&rsquo;t use the banking system, you can simply go to the nearest ATM and swap some sats for local currency.</p> <h2 id="conclusion">Conclusion</h2> <p>The possibilities of blockchain and Bitcoin are vast, and viewing Bitcoin as just another currency is like seeing only the tip of the iceberg. I think the author does a good job of getting that idea across to the reader, but he also warns that <strong>Bitcoin is still an experiment</strong>. It&rsquo;s unwise to put money you can&rsquo;t afford to lose into bitcoins.</p>