Spring Cleaning for Docker

Anyone interested in this somewhat specialized article doesn’t need an explanation of what Docker is and what this virtualization tool is used for. Therefore, this article is primarily aimed at system administrators, DevOps engineers, and cloud developers. For those who aren’t yet completely familiar with the technology, I recommend our Docker course: From Zero to Hero.

In a scenario where we regularly create new Docker images and instantiate various containers, our hard drive is put under considerable strain. Depending on their complexity, images can easily reach several hundred megabytes to gigabytes in size. To prevent creating new images from feeling like downloading a three-minute MP3 with a 56k modem, Docker uses a build cache. However, if there’s an error in the Dockerfile, this build cache can become quite bothersome. Therefore, it’s a good idea to clear the build cache regularly. Old container instances that are no longer in use can also lead to strange errors. So, how do you keep your Docker environment clean?

While docker rm <container-nane> and docker rmi <image-id> will certainly get you quite far, in build environments like Jenkins or server clusters, this strategy can become a time-consuming and tedious task. But first, let’s get an overview of the overall situation. The command docker system df will help us with this.

root:/home# docker system df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          15        9         5.07GB    2.626GB (51%)
Containers      9         7         11.05MB   5.683MB (51%)
Local Volumes   226       7         6.258GB   6.129GB (97%)
Build Cache     0         0         0B        0B

Before I delve into the details, one important note: The commands presented are very efficient and will irrevocably delete the corresponding areas. Therefore, only use these commands in a test environment before using them on production systems. Furthermore, I’ve found it helpful to also version control the commands for instantiating containers in your text file.

The most obvious step in a Docker system cleanup is deleting unused containers. Specifically, this means that the delete command permanently removes all instances of Docker containers that are not running (i.e., not active). If you want to perform a clean slate on a Jenkins build node before deployment, you can first terminate all containers running on the machine with a single command.

Abonnement / Subscription

[English] This content is only available to subscribers.

[Deutsch] Diese Inhalte sind nur für Abonnenten verfügbar.

The -f parameter suppresses the confirmation prompt, making it ideal for automated scripts. Deleting containers frees up relatively little disk space. The main resource drain comes from downloaded images, which can also be removed with a single command. However, before images can be deleted, it must first be ensured that they are not in use by any containers (even inactive ones). Removing unused containers offers another practical advantage: it releases ports blocked by containers. A port in a host environment can only be bound to a container once, which can quickly lead to error messages. Therefore, we extend our script to include the option to delete all Docker images not currently used by containers.

Abonnement / Subscription

[English] This content is only available to subscribers.

[Deutsch] Diese Inhalte sind nur für Abonnenten verfügbar.

Another consequence of our efforts concerns Docker layers. For performance reasons, especially in CI environments, you should avoid using them. Docker volumes, on the other hand, are less problematic. When you remove the volumes, only the references in Docker are deleted. The folders and files linked to the containers remain unaffected. The -a parameter deletes all Docker volumes.

docker volume prune -a -f

Another area affected by our cleanup efforts is the build cache. Especially if you’re experimenting with creating new Dockerfiles, it can be very useful to manually clear the cache from time to time. This prevents incorrectly created layers from persisting in the builds and causing unusual errors later in the instantiated container. The corresponding command is:

docker buildx prune -f

The most radical option is to release all unused resources. There is also an explicit shell command for this.

docker volume prune -a -f

We can, of course, also use the commands just presented for CI build environments like Jenkins or GitLab CI. However, this might not necessarily lead to the desired result. A proven approach for Continuous Integration/Continuous Deployment is to set up your own Docker registry where you can deploy your self-built images. This approach provides a good backup and caching system for the Docker images used. Once correctly created, images can be conveniently deployed to different server instances via the local network without having to constantly rebuild them locally. This leads to a proven approach of using a build node specifically optimized for Docker images/containers to optimally test the created images before use. Even on cloud instances like Azure and AWS, you should prioritize good performance and resource efficiency. Costs can quickly escalate and seriously disrupt a stable project.

In this article, we have seen that in-depth knowledge of the tools used offers several opportunities for cost savings. The motto “We do it because we can” is particularly unhelpful in a commercial environment and can quickly degenerate into an expensive waste of resources.


Vibe coding – a new plague of the internet?

When I first read the term vibe coding, I first thought of headphones, chill music and transitioning into flow. The absolute state of creativity that programmers chase. A rush of productivity. But no, it became clear to me quite quickly that it was about something else.
Vibe coding is the name given to what you enter into an AI via the prompt in order to get a usable program. The output of the Large Language Model (LLM) is not yet the executable program, but rather just the corresponding source text in the programming language that the Vibe Coder specifies. Therefore, depending on which platform it is on, the Vibe Coder still needs the ability to make the whole thing work.

Since I’ve been active in IT, the salespeople’s dream has been there: You no longer need programmers to develop applications for customers. So far, all approaches of this kind have been less than successful, because no matter what you did, there was no solution that worked completely without programmers. A lot has changed since the general availability of AI systems and it is only a matter of time before LLM systems such as Copilot etc. also deliver executable applications.

The possibilities that Vibe Coding opens up are quite remarkable if you know what you are doing. Straight from Goethe’s sorcerer’s apprentice, who was no longer able to master the spirits he summoned. Are programmers now becoming obsolete? In the foreseeable future, I don’t think the programming profession will die out. But a lot will change and the requirements will be very high.

I can definitely say that I am open to AI assistance in programming. However, my experiences so far have taught me to be very careful about what the LLMs suggest as a solution. Maybe it’s because my questions were very specific and for specific cases. The answers were occasionally a pointer in a possible direction that turned out to be successful. But without your own specialist knowledge and experience, all of the AI’s answers would not have been usable. Justifications or explanations should also be treated with caution in this context.

There are now various offers that want to teach people how to use artificial intelligence. So in plain language, how to formulate a functioning prompt. I think such offers are dubious, because the LLMs were developed to understand natural (human) language. So what should you learn to formulate complete and understandable sentences?

Anyone who creates an entire application using Vibe Coding must test it extensively. So click through the functions and see if everything works as it should. This can turn into a very annoying activity that becomes more annoying with each run.

The use of programs created by Vibe Coding is also unproblematic as long as they run locally on your own computer and are not freely accessible as a commercial Internet service. Because this is exactly where the danger lurks. The programs created by Vibe Coding are not sufficiently protected against hacker attacks, which is why they should only be operated in closed environments. I can also well imagine that in the future the use of programs that are Vibe Coded will be prohibited in security-critical environments such as authorities or banks. As soon as the first cyber attacks on company networks through Vibe coding programs become known, the bans are in place.

Besides the question of security for Vibe Coding applications, modifications and extensions will be extremely difficult to implement. This phenomenon is well-known in software development and occurs regularly with so-called legacy applications. As soon as you hear that something has grown organically over time, you’re already in the thick of it. A lack of structure and so-called technical debt cause a project to erode over time to such an extent that the impact of changes on the remaining functionality becomes very difficult to assess. It is therefore likely that there will be many migration projects in the future to convert AI-generated codebases back into clean structures. For this reason, Vibe Coding is particularly suitable for creating prototypes to test concepts.

There are now also complaints in open source projects that every now and then there are contributions that convert almost half of the code base and add faulty functions. Of course, common sense and the many standards established in software development help here. It’s not like we haven’t had experience with bad code commits in open source before. This gave rise to the dictatorship workflow for tools like Git, which was renamed Pull Request by the code hosting platform GitHub.

So how can you quickly identify bad code? My current prescription is to check test coverage for added code. No testing, no code merge. Of course, test cases can also be Vibe Coded or lack necessary assertions, which can now also be easily recognized automatically. In my many years in software development projects, I’ve experienced enough that no Vibe Coder can even come close to bringing beads of sweat to my forehead.

My conclusion on the subject of Vibe Coding is: In the future, there will be a shortage of capable programmers who will be able to fix tons of bad production code. So it’s not a dying profession in the foreseeable future. On the other hand, a few clever people will definitely script together a few powerful isolated solutions for their own business with simple IT knowledge that will lead to competitive advantages. As we experience this transformation, the Internet will continue to become cluttered and the gems Weizenbaum once spoke of will become harder to find.


The Golden Ratio

When we look at images, we find those particularly aesthetically pleasing whose elements follow a specific ratio of lengths to areas. This principle of harmony is called the “Golden Ratio” and is widely used in nature.

One might think that in the age of graphics rendered by artificial intelligence, we no longer need the many fundamentals of graphic design. However, this is short-sighted, because on the one hand, we have to select the best option from the generated images. To make good decisions here, knowledge of proportions and aesthetics is essential. Furthermore, we must be able to clearly articulate our wishes in order to achieve an optimal result. Only things that we truly understand can we formulate clearly and unambiguously. Therefore, sound expertise is indispensable, especially when working with generative AI.

Geometrically, the Golden Ratio means that a line segment AB is divided into two segments of different lengths (a and b). If we now set a/b equal to the sum (a+b)/a, we obtain φ with the value 1.618. Incidentally, the exact value of φ is the square root of 5 (√5). The ratios of the lengths are approximately 3:2. The following graphic illustrates this relationship.

To apply the “Golden Ratio” to shapes, you don’t need to have passed advanced math courses in high school. All we need is the number φ. If we have a rectangle with a side length of one centimeter and multiply 1 by 1.618, we get 1.618. Now we can draw a rectangle with side lengths a = 1 and b = 1.618. The resulting ratio is perfect harmony and is called the “Golden Ratio.”

If we place our square with a side length of one centimeter inside this rectangle, we get a rectangular area B that can be divided according to the same pattern. If we repeat this process a few times, we get a tiled pattern. If we now draw an arc with a radius equal to the side length inside each of the resulting squares, we get a spiral. The shape in Figure 2 should already be familiar to most, and now you also know how it is created.

The spiral just described is also found in the so-called Fibonacci sequence. The Fibonacci sequence is a simple recursive addition of the two preceding numbers. Figure 3 shows how quickly the Fibonacci sequence can be calculated. As we can see, no advanced mathematical studies are necessary.

Where do we find the Golden Ratio used? Besides proportions in logos and other graphics, the Golden Ratio is often used in typography. The height ratios of small to large letters often follow a ratio of 1:1.618.

A typical scenario for applying the Golden Ratio is the positioning of objects within a graphic. To create a good illusion of depth, the objects need a corresponding ratio of heights to each other. But the way objects are positioned relative to each other also makes an image appear calm and harmonious or agitated and restless. So we have two ways of creating a mood in the viewer using the Golden Ratio. By deliberately disrupting the proportions, we achieve a certain restlessness, which can also be desirable. Such an inverted strategy can be used, for example, in advertising to stand out from the crowd and thus attract the viewer’s attention.


Featureitis

You don’t have to be a software developer to recognize a good application. But from my own experience, I’ve often seen programs that were promising and innovative at the start mutate into unwieldy behemoths once they reach a certain number of users. Since I’ve been making this observation regularly for several decades now, I’ve wondered what the reasons for this might be.

The phenomenon of software programs, or solutions in general, becoming overloaded with details was termed “featuritis” by Brooks in his classic book, “The Mythical Man-Month.” Considering that the first edition of the book was published in 1975, it’s fair to say that this is a long-standing problem. Perhaps the most famous example of featureitis is Microsoft’s Windows operating system. Of course, there are countless other examples of improvements that make things worse.

The phenomenon of software programs, or solutions in general, becoming overloaded with details is what Brooks called “featuritis.” Windows users who were already familiar with Windows XP and then confronted with its wonderful successor Vista, only to be appeased again by Windows 7, and then nearly had a heart attack with Windows 8 and 8.1, were calmed down again at the beginning of Windows 10. At least for a short time, until the forced updates quickly brought them back down to earth. And don’t even get me started on Windows 11. The old saying about Windows was that every other version is junk and should be skipped. Well, that hasn’t been true since Windows 7. For me, Windows 10 was the deciding factor in completely abandoning Microsoft, and like many others, I bought a new operating system. Some switched to Apple, and those who couldn’t afford or didn’t want the expensive hardware, like me, opted for a Linux system. This shows how a lack of insight can quickly lead to the loss of significant market share. Since Microsoft isn’t drawing any conclusions from these developments, this fact seems to be of little concern to the company. For other companies, such events can quickly push them to the brink of collapse, and beyond.

One motivation for adding more and more features to an existing application is the so-called product life cycle, which is represented by the BCG matrix in Figure 1.

With a product’s launch, it’s not yet certain whether it will be accepted by the market. If users embrace it, it quickly rises to stardom and reaches its maximum market position as a cash cow. Once market saturation is reached, it degrades to a slow seller. So far, so good. Unfortunately, the prevailing management view is that if no growth is generated compared to the previous quarter, market saturation has already been reached. This leads to the nonsensical assumption that users must be forced to accept an updated version of the product every year. Of course, the only way to motivate a purchase is to print a bulging list of new features on the packaging.

Since well-designed features can’t simply be churned out on an assembly line, a redesign of the graphical user interface is thrown in as a free bonus every time. Ultimately, this gives the impression of having something completely new, as it requires a period of adjustment to discover the new placement of familiar functions. It’s not as if the redesign actually streamlines the user experience or increases productivity. The arrangement of input fields and buttons always seems haphazardly thrown together.

But don’t worry, I’m not calling for an update boycott; I just want to talk about how things can be improved. Because one thing is certain: thanks to artificial intelligence, the market for software products will change dramatically in just a few years. I don’t expect complex and specialized applications to be produced by AI algorithms anytime soon. However, I do expect that these applications will have enough poorly generated AI-generated code sequences, which the developer doesn’t understand, injected into their codebases, leading to unstable applications. This is why I’m rethinking clean, handcrafted, efficient, and reliable software, because I’m sure there will always be a market for it.

I simply don’t want an internet browser that has mutated into a communication hub, offering chat, email, cryptocurrency payments, and who knows what else, in addition to simply displaying web pages. I want my browser to start quickly when I click something, then respond quickly and display website content correctly and promptly. If I ever want to do something else with my browser, it would be nice if I could actively enable this through a plugin.

Now, regarding the problem just described, the argument is often made that the many features are intended to reach a broad user base. Especially if an application has all possible options enabled from the start, it quickly engages inexperienced users who don’t have to first figure out how the program actually works. I can certainly understand this reasoning. It’s perfectly fine for a manufacturer to focus exclusively on inexperienced users. However, there is a middle ground that considers all user groups equally. This solution isn’t new and is very well-known: the so-called product lines.

In the past, manufacturers always defined target groups such as private individuals, businesses, and experts. These user groups were then often assigned product names like Home, Enterprise, and Ultimate. This led to everyone wanting the Ultimate version. This phenomenon is called Fear Of Missing Out (FOMO). Therefore, the names of the product groups and their assigned features are psychologically poorly chosen. So, how can this be done better?

An expert focuses their work on specific core functions that allow them to complete tasks quickly and without distractions. For me, this implies product lines like Essentials, Pure, or Core.

If the product is then intended for use by multiple people within the company, it often requires additional features such as external user management like LDAP or IAM. This specialized product line is associated with terms like Enterprise, Company, Business, and so on.

The cluttered end result, actually intended for NOOPS, has all sorts of things already activated during installation. If people don’t care about the application’s startup and response time, then go for it. Go all out. Throw in everything you can! Here, names like Ultimate, Full, and Maximized Extended are suitable for labeling the product line. The only important thing is that professionals recognize this as the cluttered version.

Those who cleverly manage these product lines and provide as many functions as possible via so-called modules, which can be installed later, enable high flexibility even in expert mode, where users might appreciate the occasional additional feature.

If you install tracking on the module system beforehand to determine how professional users upgrade their version, you’ll already have a good idea of ​​what could be added to the new version of Essentials. However, you shouldn’t rely solely on downloads as the decision criterion for this tracking. I often try things out myself and delete extensions faster than the installation process took if I think they’re useless.

I’d like to give a small example from the DevOps field to illustrate the problem I just described. There’s the well-known GitLab, which was originally a pure code repository hosting project. The name still reflects this today. An application that requires 8 GB of RAM on a server in its basic installation just to make a Git repository accessible to other developers is unusable for me, because this software has become a jack-of-all-trades over time. Slow, inflexible, and cluttered with all sorts of unnecessary features that are better implemented using specialized solutions.

In contrast to GitLab, there’s another, less well-known solution called SCM-Manager, which focuses exclusively on managing code repositories. I personally use and recommend SCM-Manager because its basic installation is extremely compact. Despite this, it offers a vast array of features that can be added via plugins.

I tend to be suspicious of solutions that claim to be an all-in-one solution. To me, that’s always the same: trying to do everything and nothing. There’s no such thing as a jack-of-all-trades, or as we say in Austria, a miracle worker!

When selecting programs for my workflow, I focus solely on their core functionality. Are the basic features promised by the marketing truly present and as intuitive as possible? Is there comprehensive documentation that goes beyond a simple “Hello World”? Does the developer focus on continuously optimizing core functions and consider new, innovative concepts? These are the questions that matter to me.

Especially in commercial environments, programs are often used that don’t deliver on their marketing promises. Instead of choosing what’s actually needed to complete tasks, companies opt for applications whose descriptions are crammed with buzzwords. That’s why I believe that companies that refocus on their core competencies and use highly specialized applications for them will be the winners of tomorrow.


Mismanagement and Alpha Geeks

When I recently picked up Camille Fournier’s book “The Manager’s Path,” I was immediately reminded of Tom DeMarco. He wrote the classic “Peopleware” and, in the early 2000s, published “Adrenaline Junkies and Form Junkies.” It’s a list of stereotypes you might encounter in software projects, with advice on how to deal with them. After several decades in the business, I can confirm every single word from my own experience. And it’s still relevant today, because people are the ones who make projects, and we all have our quirks.

For projects to be successful, it’s not just technical challenges that need to be overcome. Interpersonal relationships also play a crucial role. One important factor in this context, which often receives little attention, is project management. There are shelves full of excellent literature on how to become a good manager. The problem, unfortunately, is that few who hold this position actually fulfill it, and even fewer are interested in developing their skills further. The result of poor management is worn-down and stressed teams, extreme pressure in daily operations, and often also delayed delivery dates. It’s no wonder, then, that this impacts product quality.

One of the first sayings I learned in my professional life was: “Anyone who thinks a project manager actually manages projects also thinks a butterfly folds lemons.” So it seems to be a very old piece of wisdom. But what is the real problem with poor management? Anyone who has to fill a managerial position has a duty to thoroughly examine the candidate’s skills and suitability. It’s easy to be impressed by empty phrases and a list of big names in the industry on a CV, without questioning actual performance. In my experience, I’ve primarily encountered project managers who often lacked the necessary technical expertise to make important decisions. It wasn’t uncommon for managers in IT projects to dismiss me with the words, “I’m not a technician, sort this out amongst yourselves.” This is obviously disastrous when the person who’s supposed to make the decisions can’t make them because they lack the necessary knowledge. An IT project manager doesn’t need to know which algorithm will terminate the project faster. Evaluations can be used to inform decisions. However, a basic understanding of programming is essential. Anyone who doesn’t know what an API is and why version compatibility prevents modules that will later be combined into a software product from working together has no right to act as a decision-maker. A fundamental understanding of software development processes and the programming paradigms used is also indispensable for project managers who don’t work directly with the code.

I therefore advocate for vetting not only the developers you hire for their skills, but also the managers who are to be brought into a company. For me, external project management is an absolute no-go when selecting my projects. This almost always leads to chaos and frustration for everyone involved, which is why I reject such projects. Managers who are not integrated into the company and whose performance is evaluated based on project success, in my experience, do not deliver high-quality work. Furthermore, internal managers, just like developers, can develop and expand their skills through mentoring, training, and workshops. The result is a healthy, relaxed working environment and successful projects.

The title of this article points to toxic stereotypes in the project business. I’m sure everyone has encountered one or more of these stereotypes in their professional environment. There’s a lot of discussion about how to deal with these individuals. However, I would like to point out that hardly anyone is born a “monster.” People are the way they are, a result of their experiences. If a colleague learns that looking stressed and constantly rushed makes them appear more productive, they will perfect this behavior over time.

Camille Fournier aptly described this with the term “The Alpha Geek.” Someone who has made their role in the project indispensable and has an answer for everything. They usually look down on their colleagues with disdain, but can never truly complete a task without others having to redo it. Unrealistic estimates for extensive tasks are just as typical as downplaying complex issues. Naturally, this is the darling of all project managers who wish their entire team consisted of these “Alpha Geeks.” I’m quite certain that if this dream could come true, it would be the ultimate punishment for the project managers who create such individuals in the first place.

To avoid cultivating “alpha geeks” within your company, it’s essential to prevent personality cults and avoid elevating personal favorites above the rest of the team. Naturally, it’s also crucial to constantly review work results. Anyone who marks a task as completed but requires rework should be reassigned until the result is satisfactory.

Personally, I share Tom DeMarco’s view on the dynamics of a project. While productivity can certainly be measured by the number of tasks completed, other factors also play a crucial role. My experience has taught me that, as mentioned earlier, it’s essential to ensure employees complete all tasks thoroughly and thoroughly before taking on new ones. Colleagues who downplay a task or offer unrealistic, low-level assessments should be assigned that very task. Furthermore, there are colleagues who, while having relatively low output, contribute significantly to team harmony.

When I talk about people who build a healthy team, I don’t mean those who simply hand out sweets every day. I’m referring to those who possess valuable skills and mentor their colleagues. These individuals typically enjoy a high level of trust within the team, which is why they often achieve excellent results as mediators in conflicts. It’s not the people who try to be everyone’s darling with false promises, but rather those who listen and take the time to find a solution. They are often the go-to people for everything and frequently have a quiet, unassuming demeanor. Because they have solutions and often lend a helping hand, they themselves receive only average performance ratings in typical process metrics. A good manager quickly recognizes these individuals because they are generally reliable. They are balanced and appear less stressed because they proceed calmly and consistently.

Of course, much more could be said about the stereotypes in a software project, but I think the points already made provide a good basic understanding of what I want to express. An experienced project manager can address many of the problems described as they arise. This naturally requires solid technical knowledge and some interpersonal skills.

Of course, we must also be aware that experienced project managers don’t just appear out of thin air. They need to be developed and supported, just like any other team member. This definitely includes rotations through all technical departments, such as development, testing, and operations. Paradigms like pair programming are excellent for this. The goal isn’t to turn a manager into a programmer or tester, but rather to give them an understanding of the daily processes. This also strengthens confidence in the skills of the entire team, and mentalities like “you have to control and push lazy and incompetent programmers to get them to lift a finger” don’t even arise. In projects that consistently deliver high quality and meet their deadlines, there’s rarely a desire to introduce every conceivable process metric.


Blockchain – an introduction

The blockchain concept is a fundamental component of various crypto payment methods such as Bitcoin and Ethereum. But what exactly is blockchain technology, and what other applications does this concept have? Essentially, blockchain is structured like a backward-linked list. Each element in the list points to its predecessor. So, what makes blockchain so special?

Blockchain extends the list concept by adding various constraints. One of these constraints is ensuring that no element in the list can be altered or removed. This is relatively easy to achieve using a hash function. We encode the content of each element in the list into a hash using a hash algorithm. A wide range of hash functions are now available, with SHA-512 being a current standard. This hash algorithm is already implemented in the standard library of almost every programming language and can be used easily. Specifically, this means that the SHA-512 hash is generated from all the data in a block. This hash is always unique and never occurs again. Thus, the hash serves as an identifier (ID) to locate a block. An entry in the block is a reference to its predecessors. This reference is the hash value of the predecessor, i.e., its ID. When implementing a blockchain, it is essential that the hash value of the predecessor is included in the calculation of the hash value of the current block. This detail ensures that elements in the blockchain can only be modified with great difficulty. Essentially, to manipulate the element one wishes to alter, all subsequent elements must also be changed. In a large blockchain with a very large number of blocks, such an undertaking entails an enormous computational effort that is very difficult, if not impossible, to accomplish.

This chaining provides us with a complete transaction history. This also explains why crypto payment methods are not anonymous. Even though the effort required to uniquely identify a transaction participant can be enormous, if this participant also uses various obfuscation methods with different wallets that are not linked by other transactions, the effort increases exponentially.

Of course, the mechanism just described still has significant weaknesses. Transactions, i.e., the addition of new blocks, can only be considered verified and secure once enough successors have been added to the blockchain to ensure that changes are more difficult to implement. For Bitcoin and similar cryptocurrencies, a transaction is considered secure if there are five subsequent transactions.

To avoid having just one entity storing the transaction history—that is, all the blocks of the blockchain—a decentralized approach comes into play. This means there is no central server acting as an intermediary. Such a central server could be manipulated by its operator. With sufficient computing power, this would allow for the rebuilding of even very large blockchains. In the context of cryptocurrencies, this is referred to as chain reorganization. This is also the criticism leveled at many cryptocurrencies. Apart from Bitcoin, no other decentralized and independent cryptocurrency exists. If the blockchain, with all its contained elements, is made public and each user has their own instance of this blockchain locally on their computer, where they can add elements that are then synchronized with all other instances of the blockchain, then we have a decentralized approach.

The technology for decentralized communication without an intermediary is called peer-to-peer (P2P). P2P networks are particularly vulnerable in their early stages, when there are only a few participants. With a great deal of computing power, one could easily create a large number of so-called “Zomi peers” that influence the network’s behavior. Especially in times when cloud computing, with providers like AWS and Google Cloud Platform, can provide virtually unlimited resources for relatively little money, this is a significant problem. This point should not be overlooked, particularly when there is a high financial incentive for fraudsters.

There are also various competing concepts within P2P. To implement a stable and secure blockchain, it is necessary to use only solutions that do not require supporting backbone servers. The goal is to prevent the establishment of a master chain. Therefore, questions must be answered regarding how individual peers can find each other and which protocol they use to synchronize their data. By protocol, we mean a set of rules, a fixed framework for how interaction between peers is regulated. Since this point is already quite extensive, I refer you to my 2022 presentation for an introduction to the topic.

Another feature of blockchain blocks is that their validity can be easily and quickly verified. This simply requires generating the SHA-512 hash of the entire contents of a block. If this hash matches the block’s ID, the block is valid. Time-sensitive or time-critical transactions, such as those relevant to payment systems, can also be created with minimal effort. No complex time servers are needed as intermediaries. Each block is appended with a timestamp. This timestamp must, however, take into account the location where it is created, i.e., specify the time zone. To obscure the location of the transaction participants, all times in the different time zones can be converted to the current UTC 0.

To ensure that the time is correctly set on the system, a time server can be queried for the current time when the software starts, and a correction message can be displayed if there are any discrepancies.

Of course, time-critical transactions are subject to a number of challenges. It must be ensured that a transaction was carried out within a defined time window. This is a problem that so-called real-time systems have to deal with. The double-spending problem also needs to be prevented—that is, the same amount being sent twice to different recipients. In a decentralized network, this requires confirmation of the transaction by multiple participants. Classic race conditions can also pose a problem. Race conditions can be managed by applying the Immutable design pattern to the block elements.

To prevent the blockchain from being disrupted by spam attacks, we need a solution that makes creating a single block expensive. We achieve this by incorporating computing power. The participant creating a block must solve a puzzle that requires a certain amount of computing time. If a spammer wants to flood the network with many blocks, their computing power increases exorbitantly, making it impossible for them to generate an unlimited number of blocks in a short time. This cryptographic puzzle is called a nonce, which stands for “number used only once.” The nonce mechanism in the blockchain is also often referred to as Proof of Work (POW) and is used in Bitcoin to verify the blocks by the miners.

The nonce is a (pseudo)random number for which a hash must be generated. This hash must then meet certain criteria. These could be, for example, two or three leading zeros in the hash. To prevent arbitrary hashes from being inserted into the block, the random number that solves the puzzle is stored directly. A nonce that has already been used cannot be used again, as this would circumvent the puzzle. When generating the hash from the nonce, it must meet the requirements, such as leading zeros, to be accepted.

Since finding a valid nonce becomes increasingly difficult as the number of blocks in a blockchain grows, it is necessary to change the rules for such a nonce cyclically, for example, every 2048 blocks. This also means that the rules for a valid nonce must be assigned to the corresponding blocks. Such a set of rules for the nonce can easily be formulated using a regular expression (regex).

We’ve now learned a considerable amount about the ruleset for a blockchain. So it’s time to consider performance. If we were to simply store all the individual blocks of the blockchain in a list, we would quickly run out of memory. While it’s possible to store the blocks in a local database, this would negatively impact the blockchain’s speed, even with an embedded solution like SQLite. A simple solution would be to divide the blockchain into equal parts, called chunks. A chunk would have a fixed length of 2048 valid blocks, and the first block of a new chunk would point to the last block of the previous chunk. Each chunk could also contain a central rule for the nonce and store metadata such as minimum and maximum timestamps.

To briefly recap our current understanding of the blockchain ruleset, we’re looking at three different levels. The largest level is the blockchain itself, which contains fundamental metadata and configurations. Such configurations include the hash algorithm used. The second level consists of so-called chunks, which contain a defined set of block elements. As mentioned earlier, chunks also contain metadata and configurations. The smallest element of the blockchain is the block itself, which comprises an ID, the described additional information such as a timestamp and nonce, and the payload. The payload is a general term for any data object that is to be made verifiable by the blockchain. For Bitcoin and other cryptocurrencies, the payload is the information about the amount being transferred from Wallet A (source) to Wallet B (destination).

Blockchain technology is also suitable for many other application scenarios. For example, the hash values ​​of open-source software artifacts could be stored in a blockchain. This would allow users to download binary files from untrusted sources and verify them against the corresponding blockchain. The same principle could be applied to the signatures of antivirus programs. Applications and other documents could also be transmitted securely in governmental settings. The blockchain would function as a kind of “postal stamp.” Accounting, including all receipts for goods and services purchased and sold, is another conceivable application.

Depending on the use case, an extension of the blockchain would be the unique signing of a block by its creator. This would utilize the classic PKI (Public Key Infrastructure) method with public and private keys. The signer stores their public key in the block and creates a signature using their private key via the payload, which is then also stored in the block.

Currently, there are two freely available blockchain implementations: BitcoinJ and Web3j for Ethereum. Of course, it’s possible to create your own universally applicable blockchain implementation using the principles just described. The pitfalls, naturally, lie in the details, some of which I’ve already touched upon in this article. Fundamentally, however, blockchain isn’t rocket science and is quite manageable for experienced developers. Anyone considering trying their hand at their own implementation now has sufficient basic knowledge to delve deeper into the necessary details of the various technologies involved.


Privacy

I constantly encounter statements like, “I use Apple because of the data privacy,” or “There are no viruses under Linux,” and so on and so forth. In real life, I just chuckle to myself and refrain from replying. These people are usually devotees of a particular brand, which they worship and would even defend with their lives. Therefore, I save my energy for more worthwhile things, like writing this article.

My aim is to use as few technical details and jargon as possible so that people without a technical background can also access this topic. Certainly, some skeptics might demand proof to support my claims. To them, I say that there are plenty of keywords for each statement that you can use to search for yourself and find plenty of primary sources that exist outside of AI and Wikipedia.

When one ponders what freedom truly means, one often encounters statements like: “Freedom is doing what you want without infringing on the freedom of others.” This definition also includes the fact that confidential information should remain confidential. However, efforts to maintain this confidentiality existed long before the availability of electronic communication devices. It is no coincidence that there is an age-old art called cryptography, which renders messages transmitted via insecure channels incomprehensible to the uninitiated. The fact that the desire to know other people’s thoughts is very old is also reflected in the saying that the two oldest professions of humankind are prostitution and espionage. Therefore, one might ask: Why should this be any different in the age of communication?

Particularly thoughtless individuals approach the topic with the attitude that they have nothing to hide anyway, so why should they bother with their own privacy? I personally belong to the group of people who consider this attitude very dangerous, as it opens the floodgates to abuse by power-hungry groups. Everyone has areas of their life that they don’t want dragged into the public eye. These might include specific sexual preferences, infidelity to a partner, or a penchant for gambling—things that can quickly shatter a seemingly perfect facade of moral integrity.

In East Germany, many people believed they were too insignificant for the notorious domestic intelligence service, the Stasi, to be interested in them. The opening of the Stasi files after German reunification demonstrated just how wrong they were. In this context, I would like to point out the existing legal framework in the EU, which boasts achievements such as hate speech laws, chat monitoring, and data retention. The private sector also has ample reason to learn more about every individual. This allows them to manipulate people effectively and encourage them to purchase services and products. One goal of companies is to determine the optimal price for their products and services, thus maximizing profit. This is achieved through methods of psychology. Or do you really believe that products like a phone that can take photos are truly worth the price they’re charged? So we see: there are plenty of reasons why personal data can indeed be highly valuable. Let’s therefore take a look at the many technological half-truths circulating in the public sphere. I’ve heard many of these half-truths from technology professionals themselves, who haven’t questioned many things.

Before I delve into the details, I’d like to make one essential point. There is no such thing as secure and private communication when electronic devices are involved. Anyone wanting to have a truly confidential conversation would have to go to an open field in strong winds, with a visibility of at least 100 meters, and cover their mouth while speaking. Of course, I realize that microphones could be hidden there as well. This statement is meant to be illustrative and demonstrates how difficult it is to create a truly confidential environment.

Let’s start with the popular brand Apple. Many Apple users believe their devices are particularly secure. This is only true to the extent that strangers attempting to gain unauthorized access to the devices face significant obstacles. The operating systems incorporate numerous mechanisms that allow users to block applications and content, for example, on their phones.

Microsoft is no different and goes several steps further. Ever since the internet became widely available, there has been much speculation about what telemetry data users send to the parent company via Windows. Windows 11 takes things to a whole new level, recording every keystroke and taking a screenshot every few seconds. Supposedly, this data is only stored locally on the computer. Of course, you can believe that if you like, but even if it were true, it’s a massive security vulnerability. Any hacker who compromises a Windows 11 computer can then read this data and gain access to online banking and all sorts of other accounts.

Furthermore, Windows 11 refuses to run on supposedly outdated processors. The fact that Windows has always been very resource-intensive is nothing new. However, the reason for the restriction to older CPUs is different. Newer generation CPUs have a so-called security feature that allows the computer to be uniquely identified and deactivated via the internet. The key term here is Pluton Security Processor with the Trusted Platform Module (TPM 2.0).

The extent of Microsoft’s desire to collect all possible information about its users is also demonstrated by the changes to its terms and conditions around 2022. These included a new section granting Microsoft permission to use all data obtained through its products to train artificial intelligence. Furthermore, Microsoft reserves the right to exclude users from all Microsoft products if hate speech is detected.

But don’t worry, Microsoft isn’t the only company with such disclaimers in its terms and conditions. Social media platforms like Meta, better known for its Facebook and WhatsApp products, and the communication platform Zoom also operate similarly. The list of such applications is, of course, much longer. Everyone is invited to imagine the possibilities that the things already described offer.

I’ve already mentioned Apple as problematic in the area of ​​security and privacy. But Android, Google’s operating system for smart TVs and phones, also gives enormous scope for criticism. It’s not entirely without reason that you can no longer remove the batteries from these phones. Android behaves just like Windows and sends all sorts of telemetry data to its parent company. Add to that the scandal involving manufacturer Samsung, which came to light in 2025. They had a hidden Israeli program called AppCloud on their devices, the purpose of which can only be guessed at. Perhaps it’s also worth remembering when, in 2023, pagers exploded for many Palestinians and other people declared enemies by Israel. It’s no secret in the security community that Israel is at the forefront of cybersecurity and cyberattacks.

Another issue with phones is the use of so-called messengers. Besides well-known ones like WhatsApp and Telegram, there are also a few niche solutions like Signal and Session. All these applications claim end-to-end encryption for secure communication. It’s true that hackers have difficulty accessing information when they only intercept network traffic. However, what happens to the message after successful transmission and decryption on the target device is a different matter entirely. How else can the meta terms and conditions, with their already included clauses, be explained?

Considering all the aforementioned facts, it’s no wonder that many devices, such as Apple, Windows, and Android, have implemented forced updates. Of course, not everything is about total control. The issue of resilience, which allows devices to age prematurely in order to replace them with newer models, is another reason.

Of course, there are also plenty of options that promise their users exceptional security. First and foremost is the free and open-source operating system Linux. There are many different Linux distributions, and not all of them prioritize security and privacy equally. The Ubuntu distribution, published by Canonical, regularly receives criticism. For example, around 2013, the Unity desktop was riddled with ads, which drew considerable backlash. The notion that there are no viruses under Linux is also a myth. They certainly exist, and the antivirus scanner for Linux is called ClamAV; however, its use is less widespread due to the lower number of home installations compared to Windows. Furthermore, Linux users are still often perceived as somewhat nerdy and less likely to click on suspicious links. But those who have installed all the great applications like Skype, Dropbox, AI agents, and so on under Linux don’t actually have any improved security compared to the Big Tech industry.

The situation is similar with so-called “debugged” smartphones. Here, too, the available hardware, which is heavily regulated, is a problem. But everyday usability also often reveals limitations. These limitations are already evident within families and among friends, who are often reliant on WhatsApp and similar apps. Even online banking can present significant challenges, as banks, for security reasons, only offer their apps through the verified Google Play Store.

As you can see, this topic is quite extensive, and I haven’t even listed all the points, nor have I delved into them in great depth. I hope, however, that I’ve been able to raise awareness, at least to the point that smartphones shouldn’t be taken everywhere, and that more time should be spent in real life with other people, free from all these technological devices.

Count on me – Java Enums

As an experienced developer, you often think that core Java topics are more for beginners. But that doesn’t necessarily have to be the case. One reason is, of course, the tunnel vision that develops in ‘pros’ over time. You can only counteract this tunnel vision by occasionally revisiting seemingly familiar topics. Unfortunately, enumerations in Java are somewhat neglected and underutilized. One possible reason for this neglect could be the many tutorials on enums available online, which only demonstrate a trivial use case. So, let’s start by looking at where enums are used in Java.

Let’s consider a few simple scenarios we encounter in everyday development. Imagine we need to implement a hash function that can support multiple hash algorithms like MD5 and SHA-1. At the user level, we often have a function that might look something like this: String hash(String text, String algorithm); – a technically correct solution.

Another example is logging. To write a log entry, the method could look like this: void log(String message, String logLevel); and examples in this style can be continued indefinitely.

The problem with this approach is free parameters like logLevel and algorithm, which allow for only a very limited number of possibilities for correct use. The risk of developers filling these parameters incorrectly under project pressure is quite high. Typos, variations in capitalization, and so on are potential sources of error. There are certainly a number of such design decisions in the Java API that could be improved.

As is often the case, there are many ways to Rome when it comes to improving the use of the API, i.e., the functions used externally. One approach that is quite common is the use of constants. To illustrate the solution a little better, I’ll take the example of hash functionality. The two hash algorithms MD5 and SHA-1 should be supported in the Hash class.

void Hash {
	
	public final static MD5 = "1";
	public final static SHA-1 = "2";

	/**
	 * Creates a hash from text by using the chosen algorithm.
	 */
	public String calculateHash(String text, String algorithm) {
		String hash = "";

		if(algorithm.equals(MD5)) {
			hash = MD5.hash(text);
		}
		if(algorithm.equals(SHA-1)) {
			hash = SHA-1.hash(text);
		}
		return hash;
	}
}

// Call
calculateHash("myHashValue", Hash.MD5);

In the Hash class, the two constants MD5 and SHA-1 are defined to indicate which values ​​are valid for the parameter algorithm. Developers with some experience recognize this configuration quite quickly and use it correctly. Technically, the solution is absolutely correct. However, even if something is syntactically correct, it doesn’t necessarily mean it’s an optimal solution in the semantic context. The strategy presented here disregards the design principles of object-oriented programming. Although we already provide valid input values ​​for the parameter algorithm through the constants, there’s no mechanism that enforces their use.

One might argue that this isn’t important as long as the method is called with a valid parameter. But that’s not the case. Every call to the calculateHash method without using the constants will lead to potential problems during refactoring. For example, if you want to change the value “2” to SHA-1 for consistency, this will lead to errors wherever the constants aren’t used. So, how can we improve this? The preferred solution should be the use of enums.

All enums defined in Java are derived from the class java.lang.Enum [2]. A selection of the methods is:

  • name() returns the name of this enum constant exactly as it appears in its enum declaration.
  • ordinal() returns the ordinal number of this enum constant (its position in the enum declaration, with the first constant assigned ordinal number zero).
  • toString() returns the name of this enum constant exactly as it appears in the declaration.

Dies ist die einfachste Variante, wie man ein Enum definieren kann. Eine einfache Aufzählung der gültigen Werte als Konstanten. In dieser Notation sind Konstantenname und Konstantenwert identisch. Wichtig beim Umgang mit Enums ist sich bewusst zu sein das die Namenkonventionen für Konstanten gelten.

The names of variables declared as class constants and of ANSI constants should be written exclusively in uppercase letters, with underscores (“_”) separating the words. (ANSI constants should be avoided to simplify debugging.) Example: static final int GET_THE_CPU = 1;

[1] Oracle Documentation

This property leads us to the first problem with the simplified definition of enums. If, for whatever reason, we want to define SHA-1 instead of SHA-1, this will result in an error, since “-” does not conform to the convention for constants. Therefore, the following construct is most commonly used for defining enums:

public enum HashAlgorithm {

    MD5("MD5"),
    SHA1("SHA-1");

    private String value;

    HashAlgorithm(final String value) {
        this.value = value;
    }

    public String getValue() {
        return this.value;
    }
}

In this example, the constant SHA1 is extended with the string value “SHA-1”. To access this value, it’s necessary to implement a way to retrieve it. This access is provided via the variable value, which has the corresponding getter getValue(). To populate the variable value, we also need a constructor. The following test case demonstrates the different ways to access the entry SHA1 and their corresponding outputs.

public class HashAlgorithmTest {

    @Test
    void enumNames() {
        assertEquals("SHA1",
                HashAlgorithm.SHA1.name());
    }

    @Test
    void enumToString() {
        assertEquals("SHA1",
                HashAlgorithm.SHA1.toString());
    }
    
    @Test
    void enumValues() {
        assertEquals("SHA-1",
                HashAlgorithm.SHA1.getValue());
    }


    @Test
    void enumOrdinals() {
        assertEquals(1,
                HashAlgorithm.SHA1.ordinal());
    }
}

First, we access the name of the constant, which in our example is SHA1. We obtain the same result using the toString() method. We retrieve the content of SHA1, i.e., the value SHA-1, by calling our defined getValue() method. Additionally, we can query the position of SHA1 within the enum. In our example, this is the second position where SHA1 occurs, and therefore has the value 1. Let’s look at the change for enums in the implementation of the hash class from Listing 1.

public String calculateHash(String text, HashAlgorithm algorithm) {
    String hash = "";
    switch (algorithm) {
        case MD5:
		hash = MD5.hash(text);
            break;
        case SHA1:
		hash = SHA1.hash(text);
            break;
    }
    return hash;
}

//CaLL
calculateHash("text2hash", HashAlgorithm.MD5);

In this example, we see a significant simplification of the implementation and also achieve a secure use for the algorithm parameter, which is now determined by the enum. Certainly, there are some critics who might complain about the amount of code to be written and point out that it could be done much more compactly. Such arguments tend to come from people who have no experience with large projects involving millions of lines of source code or who don’t work in a team with multiple programmers. Object-oriented design isn’t about avoiding every unnecessary character, but about writing readable, fault-tolerant, expressive, and easily modifiable code. We have covered all these attributes in this example using enums.

According to Oracle’s tutorial for Java enums [3], much more complex constructs are possible. Enums behave like Java classes and can also be enriched with logic. The specification primarily provides the compare() and equals() methods, which enable comparison and sorting. For the hash algorithm example, this functionality would not add any value. It’s also generally advisable to avoid placing additional functionality or logic within enums, as these should be treated similarly to pure data classes.

The fact that enums are indeed an important topic in Java development is demonstrated by the fact that J. Bloch dedicated an entire chapter of nearly 30 pages to them in his book “Effective Java.”

With this, we have worked through a comprehensive example of the correct use of enums and, above all, learned where this language construct finds meaningful application in Java.

Resources

Abonnement / Subscription

[English] This content is only available to subscribers.

[Deutsch] Diese Inhalte sind nur für Abonnenten verfügbar.


Installing Python programs via PIP on Linux

Many years ago, the scripting language Python, named after the British comedy troupe, replaced the venerable Perl on Linux. This means that every Linux distribution includes a Python interpreter by default. A pretty convenient thing, really. Or so it seems! If it weren’t for the pesky issue of security. But let’s start at the beginning, because this short article is intended for people who want to run software written in Python on Linux, but who don’t know Python or have any programming experience. Therefore, a little background information to help you understand what this is all about.

All current Linux distributions derived from Debian, such as Ubuntu, Mint, and so on, throw a cryptic error when you try to install a Python program. To prevent important system libraries written in Python from being overwritten by the installation of additional programs and causing malfunctions in the operating system, a safeguard has been built in. Unfortunately, as is so often the case, the devil is in the details.

ed@P14s:~$ python3 -m pip install ansible
error: externally-managed-environment

× This environment is externally managed
╰─> To install Python packages system-wide, try apt install
    python3-xyz, where xyz is the package you are trying to
    install.
    
    If you wish to install a non-Debian-packaged Python package,
    create a virtual environment using python3 -m venv path/to/venv.
    Then use path/to/venv/bin/python and path/to/venv/bin/pip. Make
    sure you have python3-full installed.
    
    If you wish to install a non-Debian packaged Python application,
    it may be easiest to use pipx install xyz, which will manage a
    virtual environment for you. Make sure you have pipx installed.
    
    See /usr/share/doc/python3.13/README.venv for more information.

As a solution, a virtual environment will now be set up. Debian 12, and also Debian 13, which was just released in August 2025, use Python version 3. Python 2 and Python 3 are not compatible with each other. This means that programs written in Python 2 will not run in Python 3 without modification.

If you want to install any program in Python, this is done by the so-called package manager. Most programming languages ​​have such a mechanism. The package manager for Python is called PIP. This is where the first complications arise. There are pip, pip3, and pipx. Such naming inconsistencies can also be found with the Python interpreter itself. Version 2 is started on the console with python, and version 3 with python3. Since this article refers to Debian 12 / Debian 13 and its derivatives, we know that at least Python 3 is used. To find out the actual Python version, you can also enter python3 -V in the shell, which shows version 3.13.5 in my case. If you try python or python2, you get an error message that the command could not be found.

Let’s first look at what pip, pip3, and pipx actually mean. PIP itself simply stands for Package Installer for Python [1]. Up to Python 2, PIP was used, and from version 3 onwards, we have PIP3. PIPX [2] is quite special and designed for isolated environments, which is exactly what we need. Therefore, the next step is to install PIPX. We can easily do this using the Linux package manager: sudo apt install pipx. To determine which PIP version is already installed on the system, we need the following command: python3 -m pipx --version, which in my case outputs 1.7.1. This means that I have the original Python 3 installed on my system, along with PIPX.

With this prerequisite, I can now install all possible Python modules using PIPX. The basic command is pipx install <module>. To create a practical example, we will now install Ansible. The use of pip and pip3 should be avoided, as they require installation and can lead to the cryptic error mentioned earlier.

Ansible [3] is a program written in Python and migrated to Python 3 starting with version 2.5. Here’s a brief overview of what Ansible itself is. Ansible belongs to the class of configuration management programs and allows for the fully automated provisioning of systems using a script. Provisioning can be performed, for example, as a virtual machine and includes setting hardware resources (RAM, HDD, CPU cores, etc.), installing the operating system, configuring the user, and installing and configuring other programs.

First, we need to install Ansible with pipx install ansible. Once the installation is complete, we can verify its success with pipx list, which in my case produced the following output:

ed@local:~$ pipx list
venvs are in /home/ed/.local/share/pipx/venvs
apps are exposed on your $PATH at /home/ed/.local/bin
manual pages are exposed at /home/ed/.local/share/man
   package ansible 12.1.0, installed using Python 3.13.5
    - ansible-community

The installation isn’t quite finished yet, as the command ansible --version returns an error message. The problem here is related to the Ansible edition. As we can see from the output of pipx list, we have the Community Edition installed. Therefore, the command is ansible-community --version, which currently shows version 12.2.0 for me.

If you prefer to type ansible instead of ansible-community in the console, you can do so using an alias. Setting the alias isn’t entirely straightforward, as parameters need to be passed to it. How to do this will be covered in another article.

Occasionally, Python programs cannot be installed via PIPX. One example is streamdeck-ui [4]. For a long time, Elgato’s StreamDeck hardware could be used under Linux with the Python-based streamdeck-ui. However, there is now an alternative called Boatswain, which is not written in Python and should be used instead. Unfortunately, installing streamdeck-ui results in an error due to its dependency on the ‘pillow’ library. If you try to use the installation script from the streamdeck-ui Git repository, you’ll find a reference to installing pip3, which is where streamdeck-ui can be obtained. When you then get to the point where you execute the command pip3 install --user streamdeck_ui, you’ll receive the error message “externally-managed-environment” that I described at the beginning of this article. Since we’re already using PIPX, creating another virtual environment for Python programs isn’t productive, as it will only lead to the same error with the pillow library.

As I’m not a Python programmer myself, but I do have some experience with complex dependencies in large Java projects, and I actually found the streamdeck-ui program to be better than Boatwain, I took a look around the GitHub repository. The first thing I noticed is that the last activity was in spring 2023, making reactivation rather unlikely. Nevertheless, let’s take a closer look at the error message to get an idea of ​​how to narrow down the problem when installing other programs.

Fatal error from pip prevented installation. Full pip output in file:
    /home/ed/.local/state/pipx/log/cmd_pip_errors.log

pip seemed to fail to build package:
    'pillow'

A look at the corresponding log file reveals that the dependency on pillow is defined as less than version 7 and greater than version 6.1, resulting in the use of version 6.2.2. Investigating what pillow actually does, we learn that it was a Python 2 library used for rendering graphics. The version used in streamdeck-ui is a fork of pillow for Python 3 and will be available in version 12 by the end of 2025. The problem could potentially be resolved by using a more recent version of pillow. However, this will most likely require adjustments to the streamdeck-ui code, as some incompatibilities in the used functions have probably existed since version 6.2.2.

This analysis shows that the probability of getting streamdeck-ui to run under pip3 is the same as with pipx. Anyone who gets the idea to downgrade to Python 2 just to get old programs running should try this in a separate, isolated environment, for example, using Docker. Python 2 hasn’t received any support through updates and security patches for many years, which is why installing it alongside Python 3 on your operating system is not a good idea.

So we see that the error message described at the beginning isn’t so cryptic after all if you simply use PIPX. If you still can’t get your program to install, a look at the error message will usually tell you that you’re trying to use an outdated and no longer maintained program.

Resources

Abonnement / Subscription

[English] This content is only available to subscribers.

[Deutsch] Diese Inhalte sind nur für Abonnenten verfügbar.


PDF generation with TP-CORE

PDF is arguably the most important interoperable document format between operating systems and devices such as computers, smartphones, and tablets. The most important characteristic of PDF is the immutability of the documents. Of course, there are limits, and nowadays, pages can easily be removed or added to a PDF document using appropriate programs. However, the content of individual pages cannot be easily modified. Where years ago expensive specialized software such as Adobe Acrobat was required to work with PDFs, many free solutions are now available, even for commercial use. For example, the common office suites support exporting documents as PDFs.

Especially in the business world, PDF is an excellent choice for generating invoices or reports. This article focuses on this topic. I describe how simple PDF documents can be used for invoicing, etc. Complex layouts, such as those used for magazines and journals, are not covered in this article.

Technically, the freely available library openPDF is used. The concept is to generate a PDF from valid HTML code contained in a template. Since we’re working in the Java environment, Velocity is the template engine of choice. TP-CORE utilizes all these dependencies in its PdfRenderer functionality, which is implemented as a facade. This is intended to encapsulate the complexity of PDF generation within the functionality and facilitate library replacement.

While I’m not a PDF generation specialist, I’ve gained considerable experience over the years, particularly with this functionality, in terms of software project maintainability. I even gave a conference presentation on this topic. Many years ago, when I decided to support PDF in TP-CORE, the only available library was iText, which at that time was freely available with version 5. Similar to Linus Torvalds with his original source control management system, I found myself in a similar situation. iText became commercial, and I needed a new solution. Well, I’m not Linus, who can just whip up Git overnight, so after some waiting, I discovered openPDF. OpenPDF is a fork of iText 5. Now I had to adapt my existing code accordingly. This took some time, but thanks to my encapsulation, it was a manageable task. However, during this adaptation process, I discovered problems that were already causing me difficulties in my small environment, so I released TP-CORE version 3.0 to achieve functional stability. Therefore, anyone using TP-CORE version 2.x will still find iText5 as the PDF solution. But that’s enough about the development history. Let’s look at how we can currently generate PDFs with TP-CORE 3.1. Here, too, my main goal is to achieve the highest possible compatibility.

Before we can begin, we need to include TP-CORE as a dependency in our Java project. This example demonstrates the use of Maven as the build tool, but it can easily be switched to Gradle.

<dependency>
    <groupId>io.github.together.modules</groupId>
    <artifactId>core</artifactId>
    <version>3.1.0</version>
</dependency>

To generate an invoice, for example, we need several steps. First, we need an HTML template, which we create using Velocity. The template can, of course, contain placeholders for names and addresses, enabling mail merge and batch processing. The following code demonstrates how to work with this.

<h1>HTML to PDF Velocity Template</h1>

<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet.</p>

<p>Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.</p>

<h2>Lists</h2>
<ul>
    <li><b>bold</b></li>
    <li><i>italic</i></li>
    <li><u>underline</u></li>
</ul>

<ol>
    <li>Item</li>
    <li>Item</li>
</ol>

<h2>Table 100%</h2>
<table border="1" style="width: 100%; border: 1px solid black;">
    <tr>
        <td>Property 1</td>
        <td>$prop_01</td>
        <td>Lorem ipsum dolor sit amet,</td>
    </tr>
    <tr>
        <td>Property 2</td>
        <td>$prop_02</td>
        <td>Lorem ipsum dolor sit amet,</td>
    </tr>
    <tr>
        <td>Property 3</td>
        <td>$prop_03</td>
        <td>Lorem ipsum dolor sit amet,</td>
    </tr>
</table>

<img src="path/to/myImage.png" />

<h2>Links</h2>
<p>here we try a simple <a href="https://together-platform.org/tp-core/">link</a></p>

template.vm

The template contains three properties: $prop_01, $prop_02, and $prop_03, which we need to populate with values. We can do this with a simple HashMap, which we insert using the TemplateRenderer, as the following example shows.

Map<String, String> properties = new HashMap<>();
properties.put("prop_01", "value 01");
properties.put("prop_02", "value 02");
properties.put("prop_03", "value 03");

TemplateRenderer templateEngine = new VelocityRenderer();
String directory = Constraints.SYSTEM_USER_HOME_DIR + "/";
String htmlTemplate = templateEngine
                .loadContentByFileResource(directory, "template.vm", properties);

The TemplateRenderer requires three arguments:

  • directory – The full path where the template is located.
  • template – The name of the Velocity template.
  • properties – The HashMap containing the variables to be replaced.

The result is valid HTML code, from which we can now generate the PDF using the PdfRenderer. A special feature is the constant SYSTEM_USER_HOME_DIR, which points to the user directory of the currently logged-in user and handles differences between operating systems such as Linux and Windows.

PdfRenderer pdf = new OpenPdfRenderer();
pdf.setTitle("Title of my own PDF");
pdf.setAuthor("Elmar Dott");
pdf.setSubject("A little description about the PDF document.");
pdf.setKeywords("pdf, html, openPDF");
pdf.renderDocumentFromHtml(directory + "myOwn.pdf", htmlTemplate);

The code for creating the PDF is clear and quite self-explanatory. It’s also possible to hardcode the HTML. Using the template allows for a separation of code and design, which also makes later adjustments flexible.

The default format is A4 with the dimensions size: 21cm 29.7cm; margin: 20mm 20mm; and is defined as inline CSS. This value can be customized using the setFormat(String format); method. The first value represents the width and the second value the height.

The PDF renderer also allows you to delete individual pages from a document.

PdfRenderer pdf = new OpenPdfRenderer();
PdfDocument original = pdf.loadDocument( new File("document.pdf") );
PdfDocument reduced = pdf.removePage(original, 1, 5);
pdf.writeDocument(reduced, "reduced.pdf");

We can see, therefore, that great emphasis was placed on ease of use when implementing the PDF functionality. Nevertheless, there are many ways to create customized PDF documents. This makes the solution of defining the layout via HTML particularly appealing. Despite the PdfRenderer’s relatively limited functionality, Java’s inheritance mechanism makes it very easy to extend the interface and its implementation with custom solutions. This possibility also exists for all other implementations in TP-CORE.

TP-CORE is also free for commercial use and has no restrictions thanks to the Apache 2 license. The source code can be found at https://git.elmar-dott.com and the documentation, including security and test coverage, can be found at https://together-platform.org/tp-core/.