[{"content":"Starting today there is a change here. Some articles are now also available in English.\nWhat This Means in Practice For the affected posts you will find a language switcher directly on the article page. It usually sits at the top in the header. A single click switches between the German and the English version of the same article. You do not land on the homepage, you stay with the same topic.\nWhy Only Some Articles Not every post gets translated. I translate the articles where it makes the most sense. These are for example basic tutorials or topics that are also relevant internationally. This keeps the effort manageable, and the most important content is still available in English.\nWhat Changes for You Nothing changes for the German version. The URLs stay the same, and all existing articles remain reachable as usual. You can find the English versions under the /en/ prefix.\nOutlook Over the coming weeks more articles will be translated step by step. If you would like a specific post in English, let me know. I will prioritize it accordingly.\n","permalink":"https://hyperblog.de/hoergen/en/posts/2026/2026-09-29-dem-hoergen-deutsch-english/","summary":"\u003cp\u003eStarting today there is a change here. Some articles are now also available in English.\u003c/p\u003e\n\u003ch2 id=\"what-this-means-in-practice\"\u003eWhat This Means in Practice\u003c/h2\u003e\n\u003cp\u003eFor the affected posts you will find a language switcher directly on the article page. It usually sits at the top in the header. A single click switches between the German and the English version of the same article. You do not land on the homepage, you stay with the same topic.\u003c/p\u003e","title":"From Now On Some Articles Are Also Available in English"},{"content":"The Starting Point The menus and dialogs should appear in English, the keyboard should remain German. QWERTZ, umlauts, the familiar layout. Sounds simple, but in KDE Plasma it\u0026rsquo;s not quite trivial, because language and keyboard layout are two completely separate areas. Under Wayland there\u0026rsquo;s an additional peculiarity, because here the compositor KWin manages the keyboard directly. I worked my way through the settings and the command line and will show what actually works.\nLanguage and Keyboard Are Separate In KDE Plasma, the interface language can be found under Regional Settings or in newer versions under Region \u0026amp; Language. The keyboard layout, on the other hand, is set under Hardware \u0026gt; Input Devices \u0026gt; Keyboard \u0026gt; Layouts.\nThat\u0026rsquo;s fine at first, because in theory you can set the language to English without touching the keyboard layout. In practice, however, the setting in the graphical interface is sometimes not enough. The locale variables, which sit deep in the system, then override the selection from the system settings.\nWayland Under Wayland, KWin is the decisive compositor. This means the keyboard is no longer managed centrally by the X server, but directly by KWin. The command localectl set-x11-keymap still has that name, but under Wayland it no longer reliably affects the session. The name is historical, and the command primarily sets the keymap for XWayland and the virtual console.\nFor the language, localectl still works:\nlocalectl set-locale LANG=en_US.UTF-8 For the keyboard layout under Wayland, the most reliable way is the KDE System Settings. There, under Hardware \u0026gt; Input Devices \u0026gt; Keyboard \u0026gt; Layouts, make sure that only German is activated. KWin then takes over the layout for the session.\nA well-known stumbling block: Sometimes KWin forgets the layout on restart. As a workaround, the file ~/.config/kxkbrc can be made read-only with chattr +i after the layout has been set correctly.\nX11 the Old Way In an X11 session, things look different. Here the X server is the central instance, and localectl directly affects the session.\nlocalectl set-x11-keymap de This command sets the keyboard layout for X11. The language is set just as under Wayland with localectl set-locale. After a restart or logging in again, the menus should appear in English while the keyboard remains German.\nSo the difference is: Under X11 the layout is controlled via localectl set-x11-keymap, under Wayland via the KDE System Settings, because KWin has control.\nThe Generic 105-key (Intl) PC Keyboard In the system settings it is usually displayed as Generic 105-key (Intl) PC or in German as Generischer 105-Tasten-PC (Intl). The abbreviation pc105 is the technical name in the XKB system, which is often used in configuration files or commands like setxkbmap -model pc105.\nThe 105-key keyboard is the standard layout for German and most European keyboards. It is based on the ISO standard, in contrast to the 104-key layout, which corresponds to the US American ANSI standard.\nThe Decisive Difference from the 104-key Layout The difference lies in exactly one additional key, which results from the shorter left Shift key. In the ISO layout, the left Shift key is shorter than in the ANSI layout, which creates space for another key to its right. This additional 105th key is therefore located to the right of the left Shift key and to the left of the Y.\nIn Germany, this key is assigned \u0026lt;, \u0026gt;, and |. The vertical bar is reached via AltGr + \u0026lt;.\nIn the 104-key layout (ANSI), this key is missing. Instead, the ANSI layout has a wider, horizontal Enter key and a longer left Shift key. The 104th key there is the backslash key above the Enter key.\nKeyboard Layout - Dead Acute Is a Good Choice Now to the keyboard layout itself. The standard assignment is sufficient for most cases. But if accents such as é, à, or ó are typed more frequently, it\u0026rsquo;s worth taking a look at the variants.\nDead acute means that only the acute key is a dead key. It is pressed first, then the vowel, and the character is composed. The advantage: The backtick (`) and the tilde (~) remain normal characters. This is practical when programming or in the console, because the backtick has an important function there.\nDead grave acute additionally turns the grave into a dead key. This makes sense if both accents are needed often, for example when writing in several Romance languages. For everyday use, however, this is usually overkill.\nI personally use dead acute because I don\u0026rsquo;t want to lose the backtick and can still type the accents quickly. For me, that\u0026rsquo;s the best balance.\nA note on Wayland: Dead keys work, but there are known incompatibilities with Electron and Chromium-based applications such as Chrome or VS Code. Native Wayland apps like Firefox often behave correctly, while Qt and KDE apps may have problems with dead keys depending on the version of systemd-localed.\nQuickly Switching Between Layouts If you do want to switch between German and English for the keyboard layout, you can check Configure layouts in the system settings under Keyboard \u0026gt; Layouts and add both. There you can also set a shortcut, such as Ctrl + Alt + K, to switch quickly.\nFor the interface language, there is no comparable quick switch. This is because the language is controlled via the locale and is not intended for a live switch. The most practical way remains localectl in the terminal.\nSources Download: https://kde.org/plasma-desktop/ Documentation: https://docs.kde.org/ ","permalink":"https://hyperblog.de/hoergen/en/posts/2026/2026-09-28-regionaleinstellung-tastaturbelegung-welche/","summary":"\u003ch2 id=\"the-starting-point\"\u003eThe Starting Point\u003c/h2\u003e\n\u003cp\u003eThe menus and dialogs should appear in English, the keyboard should remain German. QWERTZ, umlauts, the familiar layout. Sounds simple, but in KDE Plasma it\u0026rsquo;s not quite trivial, because language and keyboard layout are two completely separate areas. Under Wayland there\u0026rsquo;s an additional peculiarity, because here the compositor KWin manages the keyboard directly. I worked my way through the settings and the command line and will show what actually works.\u003c/p\u003e","title":"KDE Plasma - Setting Language and Keyboard Layout Separately"},{"content":"What is BTRFS Because I am currently intensively dealing with BTRFS and doing some research to shed light on this topic for myself, here is a list of my findings, explanations, and instructions. If you have any comments on this, you can find me in the Fediverse (About page). Otherwise, as always, I hope that this article helps one or another person to gain more clarity on the topic as well. And \u0026hellip; write more blog articles yourself!\nBTRFS stands for B-Tree File System and is a modern copy-on-write file system that was originally developed in 2007 by Chris Mason at Oracle and added to the Linux kernel 2.6.29 in 2009. It is now supported by various distributions and companies, including Debian, SUSE, Red Hat, Fujitsu, and Intel.\nThe essential difference from classic file systems such as ext4 or NTFS is that BTRFS does not overwrite data. When a file is changed, BTRFS writes the new version to a different location while the old version is retained until it is no longer needed.\nThis gives rise to several features:\nSnapshots: Point-in-time captures of the file system that initially take up hardly any additional storage space. Data integrity: BTRFS calculates checksums for data and metadata. Errors caused by faulty RAM or aging storage media are detected and can be corrected if redundancy is available (e.g., RAID1). Subvolumes: The file system can be divided into logical parts, similar to partitions, but more flexible. Transparent compression: Data can be compressed on the fly (e.g., with zstd). Integrated RAID support for RAID 0, 1, 10, 5, and 6. What Are Snapshots A snapshot is a point-in-time capture of the file system. The current state of a folder is recorded and can be restored later.\nThanks to copy-on-write, a fresh snapshot initially takes up hardly any additional storage space. It points to the same data blocks as the original. Only when changes are made do new blocks come into existence while the old version is retained in the snapshot. The additional space consumption therefore corresponds to the difference between the original and the snapshot.\nThis gives rise to typical use cases:\nBefore system updates: Create a snapshot, perform the update, roll back if there are problems. Automatic backups: Tools such as Snapper can create snapshots at fixed intervals and automatically delete old ones. Experiments: Changes can be tested and, if necessary, completely undone. Important: A snapshot is not a backup. In the event of a hardware failure, both the original and the snapshot are lost. Snapshots protect against logical errors such as faulty updates or accidental deletion, not against hardware failures. For real backups, snapshots are transferred to another medium using btrfs send and btrfs receive.\nNotes Snapshots are not a substitute for backups. A few points should be noted:\nSpace consumption: Old snapshots can take up a lot of space over time because they hold the differences from later changes. Automatic cleanup is therefore sensible. Performance: With very large amounts of data or databases, copy-on-write can slow things down. For such cases, files or subvolumes can be marked with the nodatacow attribute. Kernel and bootloader: If /boot is included in the snapshot, a rollback can become problematic if the kernel and modules no longer match. Many distributions therefore exclude /boot or handle the kernel separately. Snapshots with btrfs \u0026ldquo;tool\u0026rdquo; Snapshots can be created and managed entirely without additional tools such as Snapper or Timeshift. The necessary commands are part of btrfs-progs, which is present on every system with Btrfs support.\nCreating a Snapshot A snapshot is created with btrfs subvolume snapshot. The command requires a source subvolume and a target directory for the snapshot.\nRead-only snapshot (recommended for backups):\nsudo btrfs subvolume snapshot -r /home /snapshots/home/home_snapshot_$(date +%Y%m%d_%H%M%S) The -r option creates a read-only snapshot. This cannot be changed accidentally and is suitable for later transfer with btrfs send.\nWritable snapshot (for tests or clones):\nsudo btrfs subvolume snapshot /home /snapshots/home/home_snapshot_ro Without -r, a writable snapshot is created, which can be used like a complete copy of the file system.\nImportant: Snapshots can only be created from subvolumes, not from normal directories. If the source is not a subvolume, Btrfs returns an error. The target directory /snapshots/home/ must be located on its own subvolume or outside the source subvolume so that no recursive structure is created.\nListing Snapshots All subvolumes and snapshots can be displayed with the following command:\nsudo btrfs subvolume list / The output shows, among other things, the ID, the parent ID, and the path of each subvolume. With the -s option, only snapshots are listed; with -r, only read-only ones.\nA typical output of sudo btrfs subvolume list / looks like this:\nID 256 gen 12345 top level 5 path @ ID 257 gen 12340 top level 5 path @home ID 258 gen 12200 top level 5 path @snapshots ID 259 gen 12050 top level 5 path snapshots/home/home_snapshot_2026-03-14 ID 260 gen 11980 top level 5 path snapshots/home/home_snapshot_ro The columns in detail:\nID: The unique number of the subvolume. This is used internally by Btrfs and is not to be confused with Snapper\u0026rsquo;s snapshot number. gen: The generation, i.e., a counter that is incremented with every transaction in the file system. A higher value means that the subvolume was last changed later. top level: The ID of the parent subvolume. The value 5 stands for the top-level subvolume, i.e., the root of the Btrfs file system. path: The path of the subvolume relative to the top-level subvolume. This shows where the subvolume is located in the tree. In this example, one can see:\n@ is the root subvolume (ID 256). @home is a separate subvolume for /home (ID 257). @snapshots is its own subvolume (ID 258). snapshots/home/home_snapshot_2026-03-14 and snapshots/home/home_snapshot_ro are the snapshots that were created under the path /snapshots/home/. Note: The output shows the paths relative to the top-level subvolume, not relative to the mounted root. If /snapshots is mounted as a separate subvolume, the snapshot path appears accordingly under snapshots/home/.... If /snapshots is not mounted separately, the snapshots are located within the root subvolume and appear as @/snapshots/home/....\nWith the -s option, only snapshots are displayed:\nsudo btrfs subvolume list -s / With the -r option, only read-only subvolumes:\nsudo btrfs subvolume list -r / With the -t option, the type of subvolume (snapshot or subvolume) is also output:\nsudo btrfs subvolume list -t / Deleting a Snapshot A snapshot is not removed with rm -rf, but with:\nsudo btrfs subvolume delete /snapshots/home/home_snapshot_ro The corresponding directory is removed immediately, but the actual data blocks are deleted in the background. The command returns immediately without waiting for the data deletion to complete.\nIf you want to wait until the deletion is fully complete:\nsudo btrfs subvolume sync /snapshots/home Rolling Back a Snapshot To roll back to a snapshot, the current subvolume is moved and the snapshot is copied into its place:\n## Rename the current subvolume as a backup sudo mv /mnt/btrfs_root/@home /mnt/btrfs_root/@home_backup_$(date +%Y%m%d) ## Copy the snapshot as the new subvolume sudo btrfs subvolume snapshot \\ /snapshots/home/home_snapshot_20260301 \\ /mnt/btrfs_root/@home After mounting again, the restored state is available. The old state remains under @home_backup_... until it is manually deleted.\nBacking Up Snapshots with send/receive For real backups outside the system, btrfs send and btrfs receive are used. Both operations require read-only snapshots.\nInitial backup:\nsudo btrfs send /snapshots/home/home_snapshot_ro | btrfs receive /backupvol Incremental backup (only changes since the last snapshot):\nsudo btrfs send -p /snapshots/home/home_snapshot_ro /snapshots/home/home_snapshot_20260314 | btrfs receive /backupvol The -p option specifies the previous snapshot as the parent. Only the differences between the two snapshots are transferred.\nCreating, Configuring, and Using @snapshots The storage location for snapshots is defined in the Btrfs subvolume layout and in /etc/fstab, not in Snapper\u0026rsquo;s configuration file. In Snapper\u0026rsquo;s configuration, SUBVOLUME merely specifies for which directory snapshots should be created (e.g., /). Whether the snapshots remain in the same subvolume or are relocated is decided by the configuration via the Btrfs subvolume structure and the mount entries in /etc/fstab.\nIn the Snapper configuration, reference is made to the @snapshots definitions in /etc/fstab.\nBtrfs Option List subvol=NAME / subvolid=ID Specifies which subvolume appears under the mount point. subvol=@ mounts the subvolume @ as root. Without this option, the default subvolume is used.\ncompress=TYPE / compress-force=TYPE Enables transparent compression. Possible types: zlib (default), lzo, zstd. compress-force also compresses files that do not compress well. Important: Compression is incompatible with nodatacow – when nodatacow is active, compress is ignored.\nnodatacow Disables copy-on-write for the entire file system. Important: This option affects the whole file system, not just one subvolume. Only the options of the first mounted subvolume are effective. nodatacow implies nodatasum – checksums are no longer calculated.\nnodatasum Disables checksums for data. Is automatically enabled with nodatacow.\nautodefrag / noautodefrag Enables automatic defragmentation for small, random write operations. Not suitable for databases or VM images. Warning: Can destroy extent sharing with snapshots/reflinks and greatly increase space consumption.\ncommit=SECONDS Sets the interval for periodic commits. Default: 30 seconds. Higher values delay writing to persistent storage – in the event of a system crash, more data is lost.\nssd / nossd Tells the file system that it is running on an SSD. Btrfs then optimizes block allocation. Often detected automatically on modern kernels.\ndiscard / nodiscard / discard=async discard enables synchronous TRIM on every deletion – can impair performance. discard=async (since kernel 5.6) collects freed blocks and performs TRIM asynchronously – the preferred method. Alternatively: nodiscard and periodic fstrim.\nspace_cache / space_cache=v1 / space_cache=v2 / nospace_cache Controls the free-space cache. v2 (default since kernel 4.5) is significantly more performant for large file systems. nospace_cache disables the cache.\nacl / noacl Enables/disables POSIX ACLs. Default: acl.\nbarrier / nobarrier barrier (default) ensures that I/O operations go through the device cache and are persistently stored. nobarrier can increase performance but will definitely lead to data loss in the event of a power failure.\ndegraded Allows mounting with missing devices (e.g., with RAID). A read-write mount can fail if too many devices are missing.\nskip_balance Skips the automatic resumption of an interrupted balance operation.\nOptions for Special Use Cases Swapfile on Btrfs A swapfile must meet the following conditions:\nMust be preallocated, no holes Must be NODATACOW (implies NODATASUM) - more on this in the next chapter No compression The containing subvolume must not be snapshotted while the swapfile is active Only a single device and a single data profile Example for a swap subvolume in fstab:\nUUID=... /swap btrfs subvol=@swap,nodatacow,noatime 0 0 /etc/fstab Practical Example Here is an example fstab with the most important options.\n## \u0026lt;file system\u0026gt; \u0026lt;mount point\u0026gt; \u0026lt;type\u0026gt; \u0026lt;options\u0026gt; \u0026lt;dump\u0026gt; \u0026lt;pass\u0026gt; UUID=xxxxxxxx-xxxx-xxxx-xxxx / btrfs subvol=@,compress=zstd:3,noatime,ssd,discard=async,space_cache=v2 0 1 UUID=xxxxxxxx-xxxx-xxxx-xxxx /home btrfs subvol=@home,compress=zstd:3,noatime,ssd,discard=async,space_cache=v2 0 2 UUID=xxxxxxxx-xxxx-xxxx-xxxx /.snapshots btrfs subvol=@snapshots,noatime,ssd,space_cache=v2 0 0 Critical Limitations Regarding nodatacow and compress Options apply to the entire file system: nodatacow, nodatasum, and compress cannot be controlled per subvolume via mount options. Only the options of the first mounted subvolume count.\nchattr +C - To disable copy-on-write only for specific data without affecting the entire file system, setting the C attribute at the file or directory level is the right approach\nnodatacow + compress are mutually exclusive: If nodatacow is set, compression is automatically disabled.\nnodatacow and snapshots: Snapshots still work, but on the first write after a snapshot, CoW is forced in order to preserve the old blocks. The advantage of nodatacow is thus limited.\nnodatacow removes data integrity: Without nodatasum there are no checksums. On multi-disk systems (RAID1), after a crash it cannot be determined which copy is correct – with about a 50% probability the corrupted copy is read.\nAvoid autodefrag with snapshots: Auto-defragmentation breaks reflink and snapshot connections and can significantly increase space consumption.\nchattr +C instead of nodatacow To disable copy-on-write only for specific data (e.g., VM images or databases) without affecting the entire file system, setting the C attribute at the file or directory level is the right approach\n## Create a new, empty directory (or rename an existing one) sudo mkdir /var/lib/libvirt/images_nocow sudo chattr +C /var/lib/libvirt/images_nocow All newly created files in this directory inherit the NOCOW attribute\nFor already existing files, a workaround is necessary:\n# Rename the directory, create a new one, set the attribute, copy the data back sudo mv /var/lib/libvirt/images /var/lib/libvirt/images_old sudo mkdir /var/lib/libvirt/images sudo chattr +C /var/lib/libvirt/images sudo cp -a --reflink=never /var/lib/libvirt/images_old/. /var/lib/libvirt/images/ sudo rm -rf /var/lib/libvirt/images_old The parameter --reflink=never is crucial, because otherwise cp creates a CoW copy (reflink) by default, which does not inherit the attribute.\nImportant Limitation: Snapshots Even with chattr +C there is a limitation: When a snapshot is created, it partially breaks the NOCOW property. On the first write to a file after a snapshot, CoW is forced in order to preserve the old blocks for the snapshot. After that, NOCOW applies again until the next snapshot is created.\nFor VM images or databases that should be excluded from snapshots, it is therefore advisable to place them on a separate partition with nodatacow or possibly another file system.\nThe Standard Layout in Manjaro During automatic partitioning, Manjaro creates a large Btrfs partition with several subvolumes. The naming convention traditionally begins with an @ character.\nAlthough the exact names may vary depending on the installer version, the typical layout corresponds to what is described as a convention in the Manjaro wiki:\n@: The root file system (/). @home: The directory for user data (/home). @snapshots (optional): In the wiki, this subvolume is mentioned as an example of a possible snapshot storage location, but it is not part of the standard installation. This means: In a standard Manjaro installation, all snapshots that you create, for example with Timeshift or Snapper, are by default located within the @ subvolume under /.snapshots. A separate subvolume for snapshots is not created.\nThe Standard Layout in TUXEDO OS (Debian-based) TUXEDO OS uses Btrfs and Snapper by default to create automatic snapshots before and after package updates.\nTUXEDO states that during installation six subvolumes are created, which act as separate namespaces within a Btrfs partition. However, the exact names of these six subvolumes are not explicitly listed in the official announcement.\nWhat is known from the official TUXEDO documents:\nSnapper is preconfigured and automatically creates snapshots. A graphical tool called Btrfs Assistant is installed to manage subvolumes and snapshots. The first snapshot is created during the first boot process and is referred to as the \u0026ldquo;Factory Image\u0026rdquo;. Automation Without External Tools For regular snapshots, an entry in the crontab is sufficient. A simple shell script creates a timestamped snapshot:\n#!/bin/bash NOW=$(date +\u0026#34;%Y-%m-%d_%H:%M:%S\u0026#34;) mkdir -p /snapshots/home sudo btrfs subvolume snapshot -r /home \u0026#34;/snapshots/home/home_snapshot_${NOW}\u0026#34; This script can be executed at fixed intervals via cron or systemd-timer.\nSnapshots are not backups. They reside on the same file system. In the event of a hardware failure, both the original and the snapshot are lost. For real backups, snapshots must be transferred to another medium, for example with btrfs send. Delete old snapshots regularly. Snapshots take up storage space, even if they initially require hardly any additional space. Over time, differences accumulate. Regular cleanup is necessary. Pay attention to nested subvolumes. If a subvolume contains another subvolume, this is not backed up with the snapshot. The directory entry remains empty. For complete backups, nested subvolumes must be handled separately. Tool: Snapper - Snapshot Management Snapper is a tool for managing file system snapshots under Linux. It was originally developed by SUSE and is now available in many distributions.\nSnapper automates the creation, management, and cleanup of BTRFS snapshots. It creates snapshots manually, or also automatically during package updates, or also scheduled.\nList With snapper it is more convenient. List snapshots with\nsnapper list A typical output of snapper list looks like this:\n# | Type | Pre # | Date | User | Cleanup | Description ---+-------+-------+------------------------------+----------+-------------+--------------------------- 0 | single| | | root | | current 1 | single| | 2026-09-20 08:15:03 +0200 | root | timeline | timeline 2 | pre | | 2026-09-22 19:40:11 +0200 | root | number | zypp(packagekitd) 3 | post | 2 | 2026-09-22 19:41:57 +0200 | root | number | zypp(packagekitd) 4 | single| | 2026-09-23 03:00:01 +0200 | root | timeline | timeline 5 | pre | | 2026-09-25 14:02:33 +0200 | root | number | zypp(zypper) 6 | post | 5 | 2026-09-25 14:03:12 +0200 | root | number | zypp(zypper) The columns in detail:\n#: The number of the snapshot. This is used for snapper delete, snapper diff, or snapper undochange. Type: single for standalone snapshots, pre and post for pairs before and after a change. Pre #: For a post snapshot, the number of the associated pre snapshot. For single and pre, the column remains empty. Date: Time of creation. User: The user who triggered the snapshot (usually root). Cleanup: The cleanup strategy. timeline for scheduled snapshots, number for a fixed number, empty-pre-post for orphaned pairs. Description: Free text, for package operations e.g., zypp(zypper) or zypp(packagekitd). This example also shows why pre/post pairs should be deleted together: Snapshots 5 and 6 belong together, as do 2 and 3. A snapper delete 5-6 removes the pair 5 and 6, a snapper delete 2-3 accordingly removes the pair 2 and 3.\nChanges - diff To see changes between two snapshots, snapper diff is used:\nsudo snapper -c root diff 1..3 The output shows which files were added, deleted, or changed between the snapshots.\nDeleting When using snapper, deletion is done via:\nsudo snapper -c root delete NUMBER Here NUMBER is the number of the snapshot from snapper list. Automatically created pre/post pairs – for example before and after a package update – should always be deleted together, since they belong together. Snapper can do this with a single command:\nsudo snapper -c root delete NUMBER1-NUMBER2 This removes both snapshots of the pair in one step.\nDeletion Strategy There are situations in which only the post snapshot is removed while the pre snapshot is retained. This makes sense, for example, when:\nThe pre snapshot is to serve as a rollback point: Before an update, a pre snapshot is created. After the update, the post snapshot is normally only needed to document the changes. If you want to keep the rollback point but no longer the comparison basis, you delete only the post. Storage space becomes scarce: The pre snapshot marks the state before the change and is therefore the actually valuable one. The post snapshot is often dispensable if the change was successful. Snapper does not take effect with number cleanup: With the number cleanup strategy, Snapper prefers to delete entire pairs. If a single snapshot remains – for example because the pre was manually marked as worthy of protection – the post may have to be removed manually. In practice, deleting only the post snapshot is more of an exception. It is customary to remove both together, because the pre snapshot without an associated post is considered orphaned during cleanup and is then automatically removed via the empty-pre-post strategy.\nRolling Back Snapper offers two variants:\nRestore individual files: With snapper undochange, changes between two snapshots are undone. The snapshots themselves are located under /.snapshots/NUMBER/snapshot/ and can also be viewed directly. Roll back the complete system: With snapper rollback, the root subvolume is reset to an earlier snapshot. The prerequisite is that the desired snapshot has been booted into beforehand. Restoring Individual Files A snapshot behaves like a normal directory. When using Snapper, the snapshots are located under /.snapshots/NUMBER/snapshot/:\n##### View snapshot contents ls /.snapshots/5/snapshot/home/hoergen/ ##### Copy an individual file back from the snapshot sudo cp /.snapshots/5/snapshot/home/hoergen/.bashrc /home/hoergen/.bashrc Alternatively, snapper undochange can be used to undo changes between two snapshots:\n##### Undo changes between snapshot 5 (pre) and 6 (post) sudo snapper -c root undochange 5..6 If only certain files are to be reset, they can be specified explicitly:\nsudo snapper -c root undochange 5..6 /etc/fstab /etc/default/grub Restoring the Complete System - Rollback For completely rolling back the root file system, Snapper offers the command snapper rollback. The prerequisite is that the system has previously been booted into the desired snapshot. This is done via the boot menu that Snapper provides together with grub-btrfs. In the GRUB menu, an entry \u0026ldquo;BTRFS Snapshots\u0026rdquo; appears, from which any snapshot can be booted as the root subvolume.\nAfter booting into the snapshot, the file system is mounted read-only. The rollback is then executed with the following command:\nsudo snapper rollback Optionally with a description:\nsudo snapper rollback -d \u0026#34;Rollback after faulty update\u0026#34; Snapper automatically creates a snapshot of the state before the rollback. After a restart, the system is at the state of the selected snapshot.\nImportant: snapper rollback only works for the root subvolume /. Other subvolumes such as /home are not rolled back.\nAutomation with Snapper Manually creating snapshots is impractical in the long run. Snapper can automatically create timeline snapshots (e.g., hourly, daily, weekly) and delete old ones according to defined rules so that storage space does not run out.\nTypical configuration under /etc/snapper/configs/root:\nTIMELINE_CREATE=\u0026#34;yes\u0026#34; TIMELINE_CLEANUP=\u0026#34;yes\u0026#34; TIMELINE_LIMIT_HOURLY=\u0026#34;6\u0026#34; TIMELINE_LIMIT_DAILY=\u0026#34;7\u0026#34; TIMELINE_LIMIT_WEEKLY=\u0026#34;4\u0026#34; TIMELINE_LIMIT_MONTHLY=\u0026#34;6\u0026#34; TIMELINE_LIMIT_YEARLY=\u0026#34;2\u0026#34; FREE_LIMIT=\u0026#34;0.2\u0026#34; # Leave at least 20% free Activating the timers:\nsudo systemctl enable --now snapper-timeline.timer sudo systemctl enable --now snapper-cleanup.timer This starts the automatic snapshots and the cleanup. Before a pacman -Syu or apt upgrade, a manual snapshot with a description can additionally be created:\nsudo snapper -c root create --description \u0026#34;before the update\u0026#34; After the update, snapper diff can be used to trace what changed. If necessary, a rollback can be performed.\nTool: grub-btrfs grub-btrfs is a standalone tool that integrates the snapshots created by Snapper into the GRUB menu. After installation, an entry \u0026ldquo;BTRFS Snapshots\u0026rdquo; appears in the GRUB menu, from which any snapshot can be booted as the root subvolume. This is the prerequisite for rollback with snapper rollback.\nDefragmentation: Options and Problems The command btrfs filesystem defragment enables defragmentation of files and directory metadata during operation.\nHowever, its use is associated with considerable limitations, especially when snapshots or reflink copies are used.\nA reflink (short for \u0026ldquo;reference link\u0026rdquo;) is a special type of file copy that modern file systems such as Btrfs or XFS support. It uses the copy-on-write principle (CoW) to create a copy that initially takes up no additional storage space and is almost instantaneous.\nChecking Fragmentation Before performing defragmentation, it should be checked whether there is any relevant fragmentation at all. Btrfs does not report fragmentation automatically. The most reliable way is to measure the extents per file with filefrag and to observe the actual performance behavior.\nThere is no way to check an entire volume. It only works via filefrag.\nAn entire Btrfs volume cannot be directly checked as a whole for its degree of fragmentation. There is no command that outputs a single number or percentage for the fragmentation of the entire file system.\nThe reason lies in the way Btrfs works. Fragmentation is a state that relates to individual files, not to the volume as a whole. A volume can contain thousands of files, some of which are heavily fragmented and others not at all. There is no meaningful single metric that would summarize this.\nThere is an indirect approach via storage consumption. If btrfs filesystem df shows a large discrepancy between Size (allocated space) and Used (actually occupied space), this indicates fragmented or orphaned extents. However, this is not a measurement of fragmentation, but a reaction to a suspected cause.\nMeasuring Fragmentation of Individual Files filefrag is part of the e2fsprogs package and also works on Btrfs thanks to FIEMAP support. A file with a single extent is not fragmented; the more extents, the greater the fragmentation.\n## Check fragmentation of a single file filefrag /home/hoergen/.bashrc Example output:\n/home/hoergen/.bashrc: 3 extents found Finding the Most Fragmented Files For an overview of a directory, the output can be sorted. The number of extents is in the second column:\n## Top 10 most fragmented files in the current directory filefrag * | sort -nr -k 2 | head -10 sort -nr -k 2 thus sorts the lines by extent count, descending, with the highest number at the top.\n-n: Numeric sorting. Without this option, sort would sort alphabetically, so that e.g., 10 would come before 2. With -n, the numeric value is interpreted.\n-r: Reverse order. Instead of ascending, sorting is descending, so the largest number comes first.\n-k 2: The sort key is the second column. By default, sort separates columns by spaces. In the output of filefrag, the second column is the number of extents.\nhead -10 outputs the first ten lines of the input by default. Since the input was previously sorted in descending order by extent count, the ten most fragmented files of the directory are shown here.\nFor the home directory accordingly:\n## Top 10 most fragmented files in /home/hoergen cd /home/hoergen filefrag * | sort -nr -k 2 | head -10 For snapshots under /snapshots/home/..., a check is only worthwhile if there are actually files there that have been heavily modified. Since snapshots are generally read-only, their fragmentation no longer changes after creation.\nChecking Typical Candidates Certain directories tend to fragment more due to their usage pattern, and it may be worth considering the nodatacow mount option\n## Check log directory filefrag /var/log/journal/* | sort -nr -k 2 | head -10 ## Check temporary directory filefrag /tmp/* 2\u0026gt;/dev/null | sort -nr -k 2 | head -10 Performance as the Actual Indicator The mere extent count says nothing about actual performance. Defragmentation only makes sense when a noticeable slowdown occurs. Examples:\nA database under /home/hoergen/db/ responds noticeably more slowly to queries. A log file under /var/log/ slows down system startup. Without such perceptible effects, on systems with snapshots the risks of defragmentation (loss of extent sharing, increased storage consumption) outweigh the benefits.\nLimitation with Snapshots and Reflinks Before a file is defragmented, it should be checked whether it is connected to a snapshot or a reflink copy. If that is the case, defragmentation breaks this connection and storage consumption increases. A targeted check of whether a file is shared with a snapshot is not directly possible with built-in tools. In practice, this means: Files that are contained in the usual snapshot paths such as /snapshots/home/... or /.snapshots/NUMBER/snapshot/ should not be defragmented.\nOptions for Defragmentation Defragmentation can improve I/O performance by rewriting fragmented files into contiguous blocks. The command supports various options:\n## Defragment a single file sudo btrfs filesystem defragment /path/to/file ## Defragment a directory recursively sudo btrfs filesystem defragment -r /path/to/directory ## With compression (zstd, zlib, or lzo) sudo btrfs filesystem defragment -r -czstd /path Important: Without the -r option, only metadata is defragmented, not the file contents. For complete file system defragmentation, -r is required.\nAutomatic Defragmentation (autodefrag) The mount option autodefrag enables online defragmentation that automatically intervenes during small, random write operations. This option is suitable for desktop systems with normal usage, but is not recommended for large databases or VM images.\nActivation in /etc/fstab:\nUUID=... / btrfs defaults,autodefrag,compress=zstd 0 0 The Central Problem: Loss of Extent Sharing The most critical disadvantage affects systems with snapshots or reflink copies. The official Btrfs documentation states clearly:\nDefragmentation does not preserve extent sharing, e.g. files created by cp --reflink or existing on multiple snapshots. Due to that the data space consumption may increase.\nWhat this concretely means:\nWhen a file is defragmented that is currently shared by a snapshot or a reflink clone, the command breaks this sharing. It creates a new, private copy of the data for the defragmented file, while the snapshot continues to point to the old data blocks. Since both copies now exist separately, the storage space requirement for this data doubles.\nAn example: On a system with Snapper that creates hourly snapshots, defragmenting / can cause storage consumption to rise drastically because every defragmented file loses its connection to all existing snapshots.\nDefrag - Conclusion Aspect Assessment Performance gain Possible with fragmented files without snapshots Storage space Increases with snapshots/reflinks due to loss of extent sharing Snapshot compatibility Not given – snapshots lose their COW connection Recommendation Only for files without snapshot reference; avoid on Snapper systems For systems with Snapper or Timeshift, the following applies: Defragmentation should be avoided, unless it is applied specifically to files that are demonstrably not contained in snapshots. The storage space savings from snapshots outweigh the performance loss from fragmentation in most cases.\nDefragmenting with Benefits \u0026amp; Risks - Backup In order to be able to defragment after all, if it should really be necessary, a not really fully thought-out idea would be\ncreate a backup then delete all snapshots defragment create new snapshots create another backup Conclusion \u0026amp; Best Practice BTRFS with snapshots is a practical way to safeguard system changes. The manual commands are manageable, and with Snapper the process can be automated. For systems on which updates or experiments are frequently performed, this is a sensible setup. Switching from ext4 is not trivial, but is worth considering for a fresh installation or a second system.\nBest Practice\nStandard layout with @ and @home (or as specified by the installer) Use Snapper for automatic snapshots Manually create a snapshot before critical actions Install grub-btrfs for snapper rollback, because it integrates the snapshots into the GRUB menu. Set fstab options sparingly Speed: Current performance tests show that BTRFS cannot yet keep up with XFS, ext4, and others, aka is sometimes extremely slower. If you need performance, you should currently use a different FS. Caution\nSetting nodatacow globally autodefrag in the fstab Defragmentation on systems with snapshots Sources Official Btrfs Documentation\nbtrfs(5) Manpage – Mount options, compression, swapfile, limitations - https://manpages.debian.org/unstable/btrfs-progs/btrfs.5.en.html\nbtrfs(8) Manpage (Debian) – Toolbox for Btrfs management – https://manpages.debian.org/trixie/btrfs-progs/btrfs.8.en.html\nbtrfs(8) Manpage (Ubuntu) – Subvolume, device, and filesystem commands – https://manpages.ubuntu.com/manpages/noble/man8/btrfs.8.html\nBtrfs readthedocs – Technical documentation, zoned mode, file attributes – https://btrfs.readthedocs.io/\nFilesystem Performance - File System Performance Comparison Statistics 2026 https://commandlinux.com/statistics/file-system-performance-comparison-statistics-ext4-xfs-btrfs-zfs/\nSnapper Documentation\nsnapper-configs(5) Manpage – All configuration variables – https://manpages.opensuse.org/Tumbleweed/snapper/snapper-configs.5.en.html snapper(8) Manpage – Command reference, permissions, file paths – https://manpages.opensuse.org/Tumbleweed/snapper/snapper.8.en.html openSUSE Snapper Manpages – Overview of all Snapper manpages – https://en.opensuse.org/SDB:Snapper_Manpages SUSE Snapper Basic Concepts (PDF) – Snapshot types, default settings, storage space – https://documentation.suse.com/smart/systems-management/pdf/snapper-basic-concepts_en.pdf Distributions and Practical Guides\nManjaro Wiki – Btrfs – Subvolume concepts, snapshots, RAID, storage space – https://wiki.manjaro.org/index.php/Btrfs openSUSE Leap Reference (HTML) – Snapshot archiving, chattr +C, Snapper on LVM – https://doc.opensuse.org/documentation/leap/reference/html/book-reference/cha-snapper.html Oracle Linux Btrfs Docs – Send/receive workflow for backups – https://docs.oracle.com/en-us/iaas/oracle-linux/btrfs/ol-btrfs-creating-backups-and-using-the-btrfs-send-receive-feature.htm ","permalink":"https://hyperblog.de/hoergen/en/posts/2026/2026-09-27-btrfs-ein-ueberblick/","summary":"\u003ch2 id=\"what-is-btrfs\"\u003eWhat is BTRFS\u003c/h2\u003e\n\u003cp\u003eBecause I am currently intensively dealing with BTRFS and doing some research to shed light on this topic for myself, here is a list of my findings, explanations, and instructions. If you have any comments on this, you can find me in the Fediverse (About page). Otherwise, as always, I hope that this article helps one or another person to gain more clarity on the topic as well. And \u0026hellip; write more blog articles yourself!\u003c/p\u003e","title":"BTRFS an Overview, Snapshots \u0026 Rollbacks"},{"content":"Installing NeoVim on Manjaro Note: If you want it to be considerably simpler, but still don\u0026rsquo;t want to miss the Vim or Neovim feeling, then you can also use this Rust editor: Zed - Your last next editor - https://zed.dev/\nManjaro\u0026rsquo;s official repositories usually offer a current version of Neovim.\nWith pacman\nsudo pacman -Syu sudo pacman -S neovim lua51 After installation, you can check the version with the following command:\nnvim --version Installing Rust What Is Rustup - Toolchain Advantage of Rustup Rustup is the official toolchain manager for Rust. Without Rustup, you install Rust once via your distribution\u0026rsquo;s package management and are tied to that one version. Rustup additionally offers the following advantages:\nMultiple Toolchains in Parallel You can install the stable, beta, and nightly versions of Rust at the same time and switch between them:\nrustup install nightly rustup install beta rustup default stable rustup override set nightly # only for the current project This is particularly useful, since some crates (programs or libraries) only run on nightly, for example for experimental features.\nProject-Specific Versions With a rust-toolchain.toml file in the project folder, you define which Rust version the project uses:\n[toolchain] channel = \u0026#34;1.75.0\u0026#34; components = [\u0026#34;rustfmt\u0026#34;, \u0026#34;clippy\u0026#34;] As soon as you switch into the folder, Rustup automatically activates the matching version. This ensures that all team members and CI systems use the same version.\nPinning to Exact Versions You can specifically install a particular Rust version to ensure reproducible builds:\nrustup install 1.75.0 rustup default 1.75.0 Installing Additional Components Additional tools such as rustfmt, clippy, rust-analyzer, or rust-src can be added or removed at any time:\nrustup component add clippy rustup component remove rust-docs Target Support For cross-compilation, you can add targets for other platforms:\nrustup target add aarch64-unknown-linux-gnu rustup target add x86_64-pc-windows-gnu Updates Rustup updates the toolchains with one command:\nrustup update In contrast, with installation via pacman you depend on the distribution\u0026rsquo;s update cycles, which often lag months behind.\nAdvantage of a Toolchain A toolchain is the actual complete Rust package and consists of several components:\nrustc: the compiler cargo: the build manager and package manager rust-std: the standard library rust-docs: the local documentation optional components such as rustfmt and clippy Why This Is Important The advantage lies in the fact that all components of a toolchain are coordinated with one another. The compiler, the standard library, and the tools come from the same version. This avoids version conflicts such as can occur with manually assembled installations.\nChannel Instead of Version A toolchain is referenced via a channel (stable, beta, nightly) or via an exact version (1.75.0). This allows you to either always use the latest stable features or to rely on a fixed version when your project requires it.\nComparison: Rustup vs. Package Manager Aspect Rustup pacman Version Current Often outdated Multiple versions Yes No Channel switching Yes No Project-specific versions Yes No Installing additional components Yes Partially Cross-compilation targets Yes Limited Update cycle Independent Dependent on distribution When Is the Package Manager Enough? If you only use Rust occasionally, do not need project-specific versions, and are satisfied with your distribution\u0026rsquo;s version, installation via pacman is sufficient. For professional development, however, Rustup is clearly the better choice.\nInstalling Rustup Step 1: Install base-devel \u0026amp; Rustup from the repository\nsudo pacman -S base-devel rustup Step 2: Activate the toolchain\nrustup default stable Important: The rustup package from the Arch repositories does not install a toolchain by default. The binaries (rustc, cargo) are symbolic links to rustup, so the toolchain must be activated manually.\nStep 3: Verify the installation\nrustc --version cargo --version Development Tools After installing Rustup, you can add additional components:\nrustup component add rustfmt # Code formatting rustup component add clippy # Static analysis rustup component add rust-analyzer # Language server for IDEs Testing the Rust Installation cargo new hello_world cd hello_world cargo run NeoVim 4 Rust To program Rust in Neovim, you essentially need Rust itself (done), the language server rust-analyzer, and a Neovim LSP configuration.\nLSP - Language Server rust-analyzer rust-analyzer (the LSP server): This is the heart of code completion, error checking, and navigation. The best way is to add it as a component via rustup. rustup component add rust-analyzer Because there are occasional problems with invoking rust-analyzer, the following adjustment should also be made.\nIf the invocation does not work\nrust-analyzer --version install explicitly and then call the above command again\nsudo pacman -S rust-analyzer All of this has to do with a broken symlink/proxy. Possibly more on that later. For now, it should just work!\nNeovim Configuration # Overview of the directories ~/.config/nvim/ ├── init.lua # Entry point, loads config.lazy └── lua/ ├── config/ │ └── lazy.lua # Bootstrap \u0026amp; setup for lazy.nvim └── plugins/ ├── init.lua # Placeholder, prevents errors ├── rust.lua # rustaceanvim └── completion.lua # blink.cmp Create the configuration directory mkdir -p ~/.config/nvim Install the plugin manager lazy.vim Before you begin, make sure that Neovim (\u0026gt;= 0.8.0) and Git (\u0026gt;= 2.19.0) are installed on your system.\nnvim --version | head -n 1 \u0026amp;\u0026amp; git --version If Git is not yet installed:\nsudo pacman -S git Create configuration files First create the required folders and files in your Neovim configuration directory.\nmkdir -p ~/.config/nvim/lua/config \u0026amp;\u0026amp; mkdir -p ~/.config/nvim/lua/plugins Create or adjust init.lua Create the file ~/.config/nvim/init.lua (if it does not yet exist) and add this line:\nnvim ~/.config/nvim/init.lua require(\u0026#34;config.lazy\u0026#34;) Create bootstrap file for lazy.nvim So that lazy.nvim does not return an error message, an empty init.lua must be created in ~/.config/nvim/lua/plugins/.\necho \u0026#34;return {}\u0026#34; \u0026gt; ~/.config/nvim/lua/plugins/init.lua Create the file ~/.config/nvim/lua/config/lazy.lua nvim ~/.config/nvim/lua/config/lazy.lua with the following content. This code in lazy.lua automatically downloads the plugin lazy.nvim if it is not yet present:\n-- ~/.config/nvim/lua/config/lazy.lua -- Bootstrap lazy.nvim local lazypath = vim.fn.stdpath(\u0026#34;data\u0026#34;) .. \u0026#34;/lazy/lazy.nvim\u0026#34; if not (vim.uv or vim.loop).fs_stat(lazypath) then local lazyrepo = \u0026#34;https://github.com/folke/lazy.nvim.git\u0026#34; local out = vim.fn.system({ \u0026#34;git\u0026#34;, \u0026#34;clone\u0026#34;, \u0026#34;--filter=blob:none\u0026#34;, \u0026#34;--branch=stable\u0026#34;, lazyrepo, lazypath }) if vim.v.shell_error ~= 0 then vim.api.nvim_echo({ { \u0026#34;Failed to clone lazy.nvim:\\n\u0026#34;, \u0026#34;ErrorMsg\u0026#34; }, { out, \u0026#34;WarningMsg\u0026#34; }, { \u0026#34;\\nPress any key to exit...\u0026#34; }, }, true, {}) vim.fn.getchar() os.exit(1) end end vim.opt.rtp:prepend(lazypath) -- Leader keys must be set before lazy.nvim vim.g.mapleader = \u0026#34; \u0026#34; vim.g.maplocalleader = \u0026#34;\\\\\u0026#34; -- lazy.nvim setup require(\u0026#34;lazy\u0026#34;).setup({ spec = { { import = \u0026#34;plugins\u0026#34; }, }, -- LuaRocks support with hererocks rocks = { enabled = true, hererocks = true, }, -- Colorscheme when installing install = { colorscheme = { \u0026#34;habamax\u0026#34; } }, -- Automatic update check checker = { enabled = true }, -- Performance performance = { rtp = { disabled_plugins = { \u0026#34;gzip\u0026#34;, \u0026#34;matchit\u0026#34;, \u0026#34;matchparen\u0026#34;, \u0026#34;netrwPlugin\u0026#34;, \u0026#34;tarPlugin\u0026#34;, \u0026#34;tohtml\u0026#34;, \u0026#34;tutor\u0026#34;, \u0026#34;zipPlugin\u0026#34;, }, }, }, }) Verify the installation After you have saved the configuration, restart Neovim. The plugin lazy.nvim should now install itself automatically.\nOpen lazy.nvim: Enter the command :Lazy to open the graphical interface.\nOnce lazy.nvim is running, you can proceed to the next step: setting up rustaceanvim in your plugin configuration.\nInstall the plugin rustaceanvim There are two common ways to configure this. The simplest and most modern way is a special plugin for Rust.\nThis plugin is a kind of \u0026ldquo;all-inclusive package\u0026rdquo; for Rust in Neovim. It automatically connects to rust-analyzer and brings many useful functions without you having to configure the LSP server manually.\nThe rustaceanvim entry does not go into lazy.lua, but into a separate file in the plugins folder. The lazy.lua is only responsible for the bootstrap and global configuration of lazy.nvim. Your actual plugins belong in ~/.config/nvim/lua/plugins/.\nCreate a new file:\nnvim ~/.config/nvim/lua/plugins/rust.lua The file must return a table. The rustaceanvim entry is in this table:\n-- ~/.config/nvim/lua/plugins/rust.lua return { { \u0026#34;mrcjkb/rustaceanvim\u0026#34;, version = \u0026#34;^9\u0026#34;, -- Version 9 for current Neovim versions lazy = false, -- Important: Loads the plugin immediately }, } Save the file. Restart Neovim. Enter :Lazy and possibly U to update lazy.nvim recognizes the new file and installs rustaceanvim automatically. Open a .rs file. rustaceanvim then starts rust-analyzer. Plugin blink.cmp - autocompletion Installation Create the following file:\nnvim ~/.config/nvim/lua/plugins/completion.lua With the following content\nreturn { { \u0026#39;saghen/blink.cmp\u0026#39;, -- Optional: Provides snippets for snippet completion dependencies = { \u0026#39;rafamadriz/friendly-snippets\u0026#39; }, -- Uses a release tag to download prebuilt binaries. -- This is the recommended way for the best performance. version = \u0026#39;1.*\u0026#39;, ---@module \u0026#39;blink.cmp\u0026#39; ---@type blink.cmp.Config opts = { -- \u0026#39;default\u0026#39; uses key bindings similar to those of the built-in -- completion (e.g., \u0026lt;C-y\u0026gt; to accept) keymap = { preset = \u0026#39;default\u0026#39; }, appearance = { -- \u0026#39;mono\u0026#39; for \u0026#39;Nerd Font Mono\u0026#39; to align icons correctly nerd_font_variant = \u0026#39;mono\u0026#39; }, -- By default, suggestions from LSP, file paths, -- snippets, and the current buffer are used. sources = { default = { \u0026#39;lsp\u0026#39;, \u0026#39;path\u0026#39;, \u0026#39;snippets\u0026#39;, \u0026#39;buffer\u0026#39; }, }, -- Uses the Rust implementation for the fuzzy matcher (faster), -- but automatically falls back to the Lua implementation. fuzzy = { implementation = \u0026#34;prefer_rust_with_warning\u0026#34; } }, opts_extend = { \u0026#34;sources.default\u0026#34; } } } Shortcuts blink.cmp uses the default preset by default, which is based on the built-in Neovim completion. The most important key bindings in insert mode are:\n\u0026lt;C-Space\u0026gt;: Open the menu or toggle documentation. \u0026lt;C-y\u0026gt;: Accept the selected suggestion. \u0026lt;C-e\u0026gt;: Close the menu (and undo the preview). \u0026lt;C-n\u0026gt; / \u0026lt;C-p\u0026gt; (or arrow down/up): Select the next or previous entry. \u0026lt;C-k\u0026gt;: Toggle signature help (if enabled). \u0026lt;C-b\u0026gt; / \u0026lt;C-f\u0026gt;: Scroll in the documentation preview. \u0026lt;Tab\u0026gt; / \u0026lt;S-Tab\u0026gt;: Jump between snippet placeholders. If you want different behavior, you can simply choose a different preset in your blink.cmp configuration, e.g., keymap = { preset = 'super-tab' } for tab-to-accept.\nIn addition to the shortcuts I mentioned, blink.cmp has a few more that are available depending on the configuration and context (e.g., in the command line).\nPresets In blink.cmp, presets are pre-made keyboard layouts that determine how you operate the completion menu. They are, so to speak, \u0026ldquo;packages\u0026rdquo; of key bindings for specific ways of working.\nThe Most Important Presets at a Glance You choose a preset via keymap = { preset = 'name' } in your configuration.\ndefault: Based on the built-in Neovim completion. You confirm a suggestion with \u0026lt;C-y\u0026gt; (like \u0026ldquo;Yes\u0026rdquo;). super-tab: Based on VS Code. The Tab key accepts the selected suggestion. enter: Uses the Enter key (\u0026lt;CR\u0026gt;) to accept. The Tab key remains reserved for navigating between snippet placeholders. cmdline: Is specially optimized for the command line (:). The Tab key shows the menu, inserts the first entry, or selects the next one. Common Keys in All Presets Regardless of the chosen preset, some keys are always assigned the same way, for example:\n\u0026lt;C-space\u0026gt;: Open the menu or toggle documentation. \u0026lt;C-n\u0026gt; / \u0026lt;C-p\u0026gt; (or arrow keys): Select the next or previous entry. \u0026lt;C-e\u0026gt;: Close the menu. The Most Important Presets Compared blink.cmp comes with various predefined keyboard layouts, the so-called presets. Your currently used shortcuts (\u0026lt;C-Space\u0026gt;, \u0026lt;C-y\u0026gt;, \u0026lt;C-e\u0026gt;, etc.) belong to the default preset. Other presets change the assignment fundamentally:\nFunction default super-tab enter cmdline Accept \u0026lt;C-y\u0026gt; \u0026lt;Tab\u0026gt; (smart) \u0026lt;CR\u0026gt; (Enter) \u0026lt;Tab\u0026gt; Next \u0026lt;C-n\u0026gt;, \u0026lt;Down\u0026gt; \u0026lt;C-n\u0026gt;, \u0026lt;Down\u0026gt; \u0026lt;C-n\u0026gt;, \u0026lt;Down\u0026gt; \u0026lt;Tab\u0026gt;, \u0026lt;C-n\u0026gt; Previous \u0026lt;C-p\u0026gt;, \u0026lt;Up\u0026gt; \u0026lt;C-p\u0026gt;, \u0026lt;Up\u0026gt; \u0026lt;C-p\u0026gt;, \u0026lt;Up\u0026gt; \u0026lt;S-Tab\u0026gt;, \u0026lt;C-p\u0026gt; Snippet forward \u0026lt;Tab\u0026gt; \u0026lt;Tab\u0026gt; (smart) \u0026lt;Tab\u0026gt; — So if, for example, you want Tab to accept the suggestion (as in VS Code), you change keymap = { preset = 'super-tab' } in your configuration.\nOther Useful Standard Shortcuts Regardless of the preset, there are some commands that are found in most configurations:\n\u0026lt;C-k\u0026gt;: Toggles signature help (if enabled). \u0026lt;C-b\u0026gt; / \u0026lt;C-f\u0026gt;: Scrolls up/down in the documentation preview. \u0026lt;C-l\u0026gt;: Shows the completion menu or the documentation. \u0026lt;C-e\u0026gt;: Closes the menu (and undoes an automatic preview). Shortcuts in the Command Line (cmdline) When you type in the Neovim command line (:), different rules often apply. The cmdline preset is specially optimized for this:\n\u0026lt;Tab\u0026gt;: Shows the menu, inserts the first element, or selects the next one. \u0026lt;S-Tab\u0026gt;: Selects the previous element. \u0026lt;Right\u0026gt; / \u0026lt;Left\u0026gt;: Navigates through the suggestions as in normal completion. Defining Your Own Shortcuts You can adjust the assignment at any time. If, for example, you want to use \u0026lt;CR\u0026gt; (Enter) to accept, you change the keymap entry in your blink.cmp configuration:\nkeymap = { preset = \u0026#39;enter\u0026#39; }, Or you define completely custom combinations by setting preset = 'none' and assigning the keys yourself.\nExample:\n-- ~/.config/nvim/lua/plugins/completion.lua return { { \u0026#39;saghen/blink.cmp\u0026#39;, dependencies = { \u0026#39;rafamadriz/friendly-snippets\u0026#39; }, version = \u0026#39;1.*\u0026#39;, opts = { keymap = { preset = \u0026#39;enter\u0026#39; }, -- Here the preset is chosen -- ... remaining options such as appearance, sources, fuzzy }, }, } Installation Script for Neovim, Rust, and Plugins on Manjaro For the impatient with a willingness to take risks: Here is a complete Bash script that automates all steps from your guide. It installs Neovim, Rust (via Rustup), the Rust components, and sets up the complete Neovim configuration with lazy.nvim, rustaceanvim, and blink.cmp.\n#!/usr/bin/env bash ### ### install_neovim_rust.sh ### Installs Neovim, Rust (Rustup) and sets up the Neovim configuration ### with lazy.nvim, rustaceanvim, and blink.cmp on Manjaro. ### ### Author: hoergen (template), automated ### Date: 2026-09-22 ### set -euo pipefail ### ------------------------------------------------------------------ ### Colors for output ### ------------------------------------------------------------------ GREEN=\u0026#39;\\033[0;32m\u0026#39; YELLOW=\u0026#39;\\033[1;33m\u0026#39; RED=\u0026#39;\\033[0;31m\u0026#39; NC=\u0026#39;\\033[0m\u0026#39; # No Color info() { echo -e \u0026#34;${GREEN}[INFO]${NC} $*\u0026#34;; } warn() { echo -e \u0026#34;${YELLOW}[WARN]${NC} $*\u0026#34;; } error() { echo -e \u0026#34;${RED}[ERROR]${NC} $*\u0026#34; \u0026gt;\u0026amp;2; } ### ------------------------------------------------------------------ ### 0. Check prerequisites ### ------------------------------------------------------------------ if [[ $EUID -eq 0 ]]; then error \u0026#34;Please do NOT run as root. The script uses sudo where necessary.\u0026#34; exit 1 fi if ! command -v pacman \u0026amp;\u0026gt;/dev/null; then error \u0026#34;pacman not found. This script is intended for Manjaro/Arch.\u0026#34; exit 1 fi ### ------------------------------------------------------------------ ### 1. Update system and install Neovim + Lua ### ------------------------------------------------------------------ info \u0026#34;Updating package database and system ...\u0026#34; sudo pacman -Syu --noconfirm info \u0026#34;Installing Neovim, Lua 5.1, git, base-devel, and rustup ...\u0026#34; sudo pacman -S --noconfirm --needed \\ neovim \\ lua51 \\ git \\ base-devel \\ rustup ### ------------------------------------------------------------------ ### 2. Activate Rust toolchain ### ------------------------------------------------------------------ info \u0026#34;Activating Rust stable toolchain ...\u0026#34; if ! rustup toolchain list | grep -q \u0026#39;^stable\u0026#39;; then rustup default stable else info \u0026#34;stable toolchain is already active.\u0026#34; fi ### ------------------------------------------------------------------ ### 3. Install Rust components ### ------------------------------------------------------------------ info \u0026#34;Installing Rust components: rustfmt, clippy, rust-analyzer ...\u0026#34; rustup component add rustfmt || warn \u0026#34;rustfmt could not be installed.\u0026#34; rustup component add clippy || warn \u0026#34;clippy could not be installed.\u0026#34; rustup component add rust-analyzer || warn \u0026#34;rust-analyzer (rustup) could not be installed.\u0026#34; ### Fallback: rust-analyzer from the repos if the rustup call does not work. if ! command -v rust-analyzer \u0026amp;\u0026gt;/dev/null; then warn \u0026#34;rust-analyzer not found in PATH. Installing from the repos ...\u0026#34; sudo pacman -S --noconfirm --needed rust-analyzer fi ### ------------------------------------------------------------------ ### 4. Check versions ### ------------------------------------------------------------------ info \u0026#34;Checking installations ...\u0026#34; nvim --version | head -n 1 rustc --version cargo --version rust-analyzer --version 2\u0026gt;/dev/null || warn \u0026#34;rust-analyzer --version not available.\u0026#34; ### ------------------------------------------------------------------ ### 5. Create Neovim configuration directory ### ------------------------------------------------------------------ NVIM_CONFIG=\u0026#34;$HOME/.config/nvim\u0026#34; NVIM_LUA=\u0026#34;$NVIM_CONFIG/lua\u0026#34; NVIM_PLUGINS=\u0026#34;$NVIM_LUA/plugins\u0026#34; info \u0026#34;Creating Neovim configuration directories ...\u0026#34; mkdir -p \u0026#34;$NVIM_CONFIG\u0026#34; mkdir -p \u0026#34;$NVIM_LUA/config\u0026#34; mkdir -p \u0026#34;$NVIM_PLUGINS\u0026#34; ### ------------------------------------------------------------------ ### 6. Create init.lua ### ------------------------------------------------------------------ info \u0026#34;Creating $NVIM_CONFIG/init.lua ...\u0026#34; cat \u0026gt; \u0026#34;$NVIM_CONFIG/init.lua\u0026#34; \u0026lt;\u0026lt;\u0026#39;EOF\u0026#39; require(\u0026#34;config.lazy\u0026#34;) EOF ### ------------------------------------------------------------------ ### 7. Plugin init (empty so that lazy.nvim does not throw errors) ### ------------------------------------------------------------------ info \u0026#34;Creating $NVIM_PLUGINS/init.lua ...\u0026#34; cat \u0026gt; \u0026#34;$NVIM_PLUGINS/init.lua\u0026#34; \u0026lt;\u0026lt;\u0026#39;EOF\u0026#39; return {} EOF ### ------------------------------------------------------------------ ### 8. Create lazy.lua bootstrap file ### ------------------------------------------------------------------ info \u0026#34;Creating $NVIM_LUA/config/lazy.lua ...\u0026#34; cat \u0026gt; \u0026#34;$NVIM_LUA/config/lazy.lua\u0026#34; \u0026lt;\u0026lt;\u0026#39;EOF\u0026#39; -- ~/.config/nvim/lua/config/lazy.lua -- Bootstrap lazy.nvim local lazypath = vim.fn.stdpath(\u0026#34;data\u0026#34;) .. \u0026#34;/lazy/lazy.nvim\u0026#34; if not (vim.uv or vim.loop).fs_stat(lazypath) then local lazyrepo = \u0026#34;https://github.com/folke/lazy.nvim.git\u0026#34; local out = vim.fn.system({ \u0026#34;git\u0026#34;, \u0026#34;clone\u0026#34;, \u0026#34;--filter=blob:none\u0026#34;, \u0026#34;--branch=stable\u0026#34;, lazyrepo, lazypath }) if vim.v.shell_error ~= 0 then vim.api.nvim_echo({ { \u0026#34;Failed to clone lazy.nvim:\\n\u0026#34;, \u0026#34;ErrorMsg\u0026#34; }, { out, \u0026#34;WarningMsg\u0026#34; }, { \u0026#34;\\nPress any key to exit...\u0026#34; }, }, true, {}) vim.fn.getchar() os.exit(1) end end vim.opt.rtp:prepend(lazypath) -- Leader keys must be set before lazy.nvim vim.g.mapleader = \u0026#34; \u0026#34; vim.g.maplocalleader = \u0026#34;\\\\\u0026#34; -- lazy.nvim setup require(\u0026#34;lazy\u0026#34;).setup({ spec = { { import = \u0026#34;plugins\u0026#34; }, }, -- LuaRocks support with hererocks rocks = { enabled = true, hererocks = true, }, -- Colorscheme when installing install = { colorscheme = { \u0026#34;habamax\u0026#34; } }, -- Automatic update check checker = { enabled = true }, -- Performance performance = { rtp = { disabled_plugins = { \u0026#34;gzip\u0026#34;, \u0026#34;matchit\u0026#34;, \u0026#34;matchparen\u0026#34;, \u0026#34;netrwPlugin\u0026#34;, \u0026#34;tarPlugin\u0026#34;, \u0026#34;tohtml\u0026#34;, \u0026#34;tutor\u0026#34;, \u0026#34;zipPlugin\u0026#34;, }, }, }, }) EOF ### ------------------------------------------------------------------ ### 9. rustaceanvim plugin configuration ### ------------------------------------------------------------------ info \u0026#34;Creating $NVIM_PLUGINS/rust.lua ...\u0026#34; cat \u0026gt; \u0026#34;$NVIM_PLUGINS/rust.lua\u0026#34; \u0026lt;\u0026lt;\u0026#39;EOF\u0026#39; -- ~/.config/nvim/lua/plugins/rust.lua return { { \u0026#34;mrcjkb/rustaceanvim\u0026#34;, version = \u0026#34;^9\u0026#34;, -- Version 9 for current Neovim versions lazy = false, -- Important: Loads the plugin immediately }, } EOF ### ------------------------------------------------------------------ ### 10. blink.cmp plugin configuration ### ------------------------------------------------------------------ info \u0026#34;Creating $NVIM_PLUGINS/completion.lua ...\u0026#34; cat \u0026gt; \u0026#34;$NVIM_PLUGINS/completion.lua\u0026#34; \u0026lt;\u0026lt;\u0026#39;EOF\u0026#39; -- ~/.config/nvim/lua/plugins/completion.lua return { { \u0026#39;saghen/blink.cmp\u0026#39;, -- Optional: Provides snippets for snippet completion dependencies = { \u0026#39;rafamadriz/friendly-snippets\u0026#39; }, -- Uses a release tag to download prebuilt binaries. -- This is the recommended way for the best performance. version = \u0026#39;1.*\u0026#39;, ---@module \u0026#39;blink.cmp\u0026#39; ---@type blink.cmp.Config opts = { -- \u0026#39;default\u0026#39; uses key bindings similar to those of the built-in -- completion (e.g., \u0026lt;C-y\u0026gt; to accept) keymap = { preset = \u0026#39;default\u0026#39; }, appearance = { -- \u0026#39;mono\u0026#39; for \u0026#39;Nerd Font Mono\u0026#39; to align icons correctly nerd_font_variant = \u0026#39;mono\u0026#39; }, -- By default, suggestions from LSP, file paths, -- snippets, and the current buffer are used. sources = { default = { \u0026#39;lsp\u0026#39;, \u0026#39;path\u0026#39;, \u0026#39;snippets\u0026#39;, \u0026#39;buffer\u0026#39; }, }, -- Uses the Rust implementation for the fuzzy matcher (faster), -- but automatically falls back to the Lua implementation. fuzzy = { implementation = \u0026#34;prefer_rust_with_warning\u0026#34; } }, opts_extend = { \u0026#34;sources.default\u0026#34; } } } EOF ### ------------------------------------------------------------------ ### 11. Install plugins headless (lazy.nvim sync) ### ------------------------------------------------------------------ info \u0026#34;Installing plugins via lazy.nvim (headless) ...\u0026#34; if nvim --headless \u0026#34;+Lazy! sync\u0026#34; +qa 2\u0026gt;/dev/null; then info \u0026#34;Plugins successfully synchronized.\u0026#34; else warn \u0026#34;Headless sync failed. Please start Neovim manually and run \u0026#39;:Lazy sync\u0026#39;.\u0026#34; fi ### ------------------------------------------------------------------ ### 12. Done ### ------------------------------------------------------------------ info \u0026#34;Done! Summary:\u0026#34; echo \u0026#34; - Neovim version: $(nvim --version | head -n 1)\u0026#34; echo \u0026#34; - Rust version: $(rustc --version)\u0026#34; echo \u0026#34; - Cargo version: $(cargo --version)\u0026#34; echo \u0026#34; - rust-analyzer: $(rust-analyzer --version 2\u0026gt;/dev/null || echo \u0026#39;not available\u0026#39;)\u0026#34; echo \u0026#34; - Neovim config: $NVIM_CONFIG\u0026#34; echo info \u0026#34;Start Neovim with \u0026#39;nvim\u0026#39; and check with \u0026#39;:Lazy\u0026#39; whether all plugins are installed.\u0026#34; info \u0026#34;Open a .rs file to test rustaceanvim and rust-analyzer.\u0026#34; Usage Save the file as e.g., install_neovim_rust.sh.\nMake it executable:\nchmod +x install_neovim_rust.sh Start it (not as root):\n./install_neovim_rust.sh What the Script Does Step Action 1 System update via pacman -Syu 2 Installation of neovim, lua51, git, base-devel, rustup 3 Activation of the Rust stable toolchain 4 Adding rustfmt, clippy, rust-analyzer 5 Fallback: rust-analyzer from the repos if the rustup version is not in PATH 6 Creating the directories ~/.config/nvim/lua/{config,plugins} 7 Creating init.lua with require(\u0026quot;config.lazy\u0026quot;) 8 Creating the empty plugins/init.lua 9 Creating config/lazy.lua (bootstrap + setup) 10 Creating plugins/rust.lua (rustaceanvim) 11 Creating plugins/completion.lua (blink.cmp, preset default) 12 Headless installation of the plugins via nvim --headless \u0026quot;+Lazy! sync\u0026quot; +qa Notes The script deliberately uses the default preset for blink.cmp. The custom shortcuts (e.g., super-tab or enter) are not included – you can adjust them later in ~/.config/nvim/lua/plugins/completion.lua. rust-analyzer is first installed via rustup. If that fails or the command does not end up in PATH, the package from the Manjaro repos is automatically installed afterwards. The plugin installation runs headless. If that fails, you can simply start Neovim normally and run :Lazy sync. ","permalink":"https://hyperblog.de/hoergen/en/posts/2026/2026-09-21-neovim-rust-developement/","summary":"\u003ch2 id=\"installing-neovim-on-manjaro\"\u003eInstalling NeoVim on Manjaro\u003c/h2\u003e\n\u003cblockquote class=\"book-hint tip\"\u003e\n\u003cp\u003e\u003cstrong\u003eNote:\u003c/strong\u003e If you want it to be considerably simpler, but still don\u0026rsquo;t want to miss the Vim or Neovim feeling, then you can also use this Rust editor: Zed - Your last next editor - \u003ca href=\"https://zed.dev/\"\u003ehttps://zed.dev/\u003c/a\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eManjaro\u0026rsquo;s official repositories usually offer a current version of Neovim.\u003c/p\u003e\n\u003cp\u003eWith pacman\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo pacman -Syu\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo pacman -S neovim lua51\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAfter installation, you can check the version with the following command:\u003c/p\u003e","title":"Installing Neovim \u0026 Rust - A Guide for Manjaro"},{"content":"I really like the Vim editor and I also want to use it for programming. However, doing so with the original Vim is quite complicated. So I took a closer look at NeoVim. A more modern fork of Vim. I just looked up the fundamental differences to see which one I need to use now, or whether I can simply keep using \u0026ldquo;both.\u0026rdquo; Spoiler alert: I can simply keep using both.\nAnd I will try to build a development environment in Rust that is easy to follow, and of course I will also publish the how-to for it here.\nFeature Vim Neovim Project philosophy Stability, backward compatibility; shaped by a single lead developer (Bram Moolenaar, † 2023) Community-driven, rapid iteration, focus on modernization and extensibility Configuration language Vimscript (VimL), plus Vim9script for new plugins Lua (native), Vimscript is still supported LSP support (code completion, error checking) Not built in; requires plugins such as coc.nvim or vim-lsp Natively built in (vim.lsp) Asynchronous processing Historically limited; plugins can block Native via libuv; background processes without blocking Plugin language Vimscript, Python, Ruby (often with compatibility problems) Lua, Vimscript; remote plugins via msgpack-RPC in any language Availability on servers Standard on practically every Linux/Unix system; essential for SSH workflows Rarely preinstalled; often has to be installed first Configuration directory ~/.vim/ (historical, hardcoded) ~/.config/nvim/ (follows the XDG standard) The Most Important Practical Consequence The choice depends heavily on my work environment:\nIf I work a lot on remote servers (SSH): Vim is the safe bet, because it is preinstalled almost everywhere If I want a modern, IDE-like experience locally with plugins and LSP: Neovim offers the more powerful and more modern foundation Parallel Installation - Yes! Easily doable. Vim and Neovim are completely independent programs with different binary names (vim vs. nvim), separate configuration directories, and their own plugin folders. They do not interfere with each other.\nSeparate Configurations Vim Neovim Configuration file ~/.vimrc ~/.config/nvim/init.vim or init.lua Plugin directory ~/.vim/ ~/.local/share/nvim/ Plugin manager (examples) vim-plug, Vundle lazy.nvim, packer.nvim Since both use completely separate paths, I can have different plugins, themes, and settings in each without any conflicts arising.\nPossible Overlap There is one small detail: Some distributions offer a package named vim that is actually a symlink to neovim (or vice versa). However, this is rare and only the case on systems deliberately configured that way. Normally the following applies:\nvim starts Vim nvim starts Neovim I can check this with which vim and which nvim.\nPractical Tip: Default Editor If I use both in parallel, it is worth setting Neovim as the default editor (e.g., via $EDITOR or alternatives) and keeping Vim as a fallback for servers/SSH. That way I have the modern environment locally and still always have a working editor on servers.\nSuper Flexible as an Alias If you frequently switch between the two, you can create aliases in your ~/.bashrc. For example, on Debian/Ubuntu:\nalias vim=\u0026#39;nvim\u0026#39; alias vim-old=\u0026#39;/usr/bin/vim.basic\u0026#39; On Arch and Manjaro, the alias for the original Vim would simply be alias vim-old='vim', because there vim still points to the original Vim.\nPermanent - Debian \u0026amp; Ubuntu For Ubuntu and Debian, you use update-alternatives to register Neovim as the system-wide default.\n#### Register Neovim as an alternative for vi, vim, view, and vimdiff sudo update-alternatives --install /usr/bin/vi vi /usr/bin/nvim 60 sudo update-alternatives --install /usr/bin/vim vim /usr/bin/nvim 60 sudo update-alternatives --install /usr/bin/view view /usr/bin/nvim 60 sudo update-alternatives --install /usr/bin/vimdiff vimdiff /usr/bin/nvim 60 The 60 at the end is the priority. Since Neovim is usually installed with a lower priority than Vim, this command ensures that vim and vi point to nvim.\nTo check whether it worked:\nwhich vim #### should output /usr/bin/nvim If you want to change the assignment again later, you can run sudo update-alternatives --config vim and select interactively.\nOn Ubuntu, there is also the command sudo select-editor, which shows you a list of available editors and lets you choose. This is the easiest way if you do not want to manually edit configuration files.\nPermanent - Arch \u0026amp; Manjaro For Manjaro and Arch Linux, you instead set the environment variables. This is also the cleaner way, because it works independently of the distribution.\n#### System-wide for all users sudo tee -a /etc/environment \u0026lt;\u0026lt;EOF EDITOR=nvim VISUAL=nvim EOF The variables become active for all users at the next login. One advantage: Programs that access $EDITOR or $VISUAL then automatically use Neovim.\nAs the sudo Editor A note for sudo: If you run sudo visudo or something similar, the environment variables from /etc/environment are not adopted by default, because sudo sanitizes the environment for security reasons. In that case, you can use sudo -E visudo or add the variable in the sudoers file with Defaults env_keep += \u0026quot;EDITOR VISUAL\u0026quot;.\nsudo -E visudo - temporary The -E parameter (for --preserve-env) causes sudo to completely preserve the environment for that one command. So you would always have to type sudo -E visudo.\nThat is impractical if you need it often. The entry in /etc/sudoers is the permanent solution so that you can simply use sudo visudo and still get Neovim.\nsudoers - permanent You enter the line Defaults env_keep += \u0026quot;EDITOR VISUAL\u0026quot; in the file /etc/sudoers. You never do this directly, but always via the command sudo visudo. The tool checks the syntax before the change is saved, thus preventing you from wrecking your system.\nWhen you run sudo visudo, /etc/sudoers opens in an editor. You look for the line that begins with Defaults and add your line below it or at the end of the Defaults section.\nAn excerpt from the file then looks like this:\n## Override built-in defaults Defaults env_reset Defaults env_keep += \u0026#34;EDITOR VISUAL\u0026#34; The line Defaults env_reset is usually already present. It ensures that sudo uses a clean environment. With Defaults env_keep += \u0026quot;EDITOR VISUAL\u0026quot; you tell sudo that it should keep the variables EDITOR and VISUAL from your environment.\nWhy This Is Necessary By default, sudo sanitizes the environment. So if you have set EDITOR=nvim and then run sudo visudo, sudo no longer sees this variable. It then falls back to the default editor, often vi or nano.\nWith the env_keep entry, your EDITOR variable survives the sudo call, and visudo opens with Neovim (or whatever you have set).\nSources Vim – Official repository: GitHub – vim/vim, https://github.com/vim/vim Vim – Official website: vim.org, https://www.vim.org Vim – User manual: Vim User Manual, https://vimhelp.org/usr_toc.txt.html Vim – Help documentation: Vim help files, https://vimhelp.org/ Neovim – Official website: neovim.io, https://neovim.io Neovim – GitHub repository: GitHub – neovim/neovim, https://github.com/neovim/neovim Neovim – User documentation: Neovim User Documentation, https://neovim.io/doc/user/index.html Neovim – Differences from Vim: Neovim vim_diff, https://neovim.io/doc/user/vim_diff/ ","permalink":"https://hyperblog.de/hoergen/en/posts/2026/2026-09-21-vim-vs-neovim/","summary":"\u003cp\u003eI really like the Vim editor and I also want to use it for programming. However, doing so with the original Vim is quite complicated. So I took a closer look at NeoVim. A more modern fork of Vim. I just looked up the fundamental differences to see which one I need to use now, or whether I can simply keep using \u0026ldquo;both.\u0026rdquo; Spoiler alert: I can simply keep using both.\u003c/p\u003e","title":"Vim vs Neovim - A Decision Guide"},{"content":"What Folds Actually Are In Vim, folds are collapsible and expandable regions in a text document. You can use them to collapse entire paragraphs, functions, or code blocks so that only the first line remains visible. This makes navigating large files considerably easier.\nThe mnemonic for the command is simple. All fold commands begin with z, because the z looks like a folded piece of paper viewed from the side.\nThe Six Fold Methods Vim offers six different methods for how folds are created. You set them with :set foldmethod=METHOD or enter them permanently in your ~/.vimrc.\nmanual - You create folds yourself with zf, for unstructured text indent - Indentation determines the fold depth, ideal for program code expr - Folds are defined by an expression, for log files or special filters syntax - Syntax highlighting defines the folds, ideal for program code diff - Unchanged text is folded marker - Markers in the text such as {{{ and }}} define folds For everyday use, indent for code and manual for everything else are the most practical methods.\nfoldlevel - Controlling the Fold Depth The foldlevel option determines how many levels of folds are open or closed at the same time. It is the central control for the fold depth in the current window.\nThe Typical Values foldlevel=0 - All folds are closed. You only see the outermost structure, for example chapter headings or top-level functions foldlevel=1 - Only the top level is expanded, folds beneath it remain closed. Good for an overview of the main blocks foldlevel=2 - Two levels are visible, deeper folds remain closed. Practical for nested code structures foldlevel=99 - Practically everything is open. Only explicitly closed folds remain closed foldlevel vs. foldlevelstart An important difference: foldlevel applies to the current window and takes effect immediately. foldlevelstart determines the fold depth with which a file is opened.\nSo if you want a file to be completely expanded when opened, you set foldlevelstart=99 in your ~/.vimrc.\nImportant Commands :set foldlevel? shows the current value :setlocal foldlevel=1 sets the depth only for the current window 2zM closes all folds up to level 2, leaving level 1 open Saving Folds Permanently An important point that many people do not know. When you close a file, manually created folds are lost. Vim does not remember the state automatically.\nFor this there are the commands :mkview and :loadview. With :mkview you save the current state including the folds. When you open the file again later, you load everything back with :loadview. You can save up to ten different views per file.\nStorage Location of Folds Vim saves the views in the directory defined in the viewdir option. The default value depends on your operating system:\nLinux and Unix (including macOS): ~/.vim/view Windows: $VIM/vimfiles/view In this folder, Vim creates a separate view file for each file. The file name is generated from the path of the original file, with special characters replaced by equals signs (=).\nFinding Out the Current Storage Location You can display the path directly in Vim:\n:set viewdir? The output shows you where Vim currently searches or saves.\nChanging the Storage Location If you want to change the default path (for example, to keep your home directory tidy), you can adjust the option in your ~/.vimrc:\nset viewdir=~/.vim/my-views In newer Vim versions, the default value now also respects the XDG_CONFIG_HOME environment variable and then uses ~/.config/vim/view.\nDeleting View Files Since there is no built-in command for deleting, you have to remove the files manually. Simply navigate to the folder mentioned above and delete the corresponding files.\nThe Most Important Commands for Everyday Use You should know the basic commands, then you will already get very far.\nOpening and Closing zo opens a fold under the cursor (open) zc closes a fold under the cursor (close) za toggles the fold, opens if closed, closes if open (alternate) zR opens all folds in the document (Reset) zM closes all folds in the document (Minimize) Navigation Between Folds Combination with the classic navigation commands:\nzj jumps to the next fold zk jumps to the previous fold [z jumps to the beginning of the current fold ]z jumps to the end of the current fold Creating and Deleting zf followed by a motion creates a fold, for example zfap for a whole paragraph (fold) zd deletes the fold under the cursor, the text is preserved (delete) zE deletes all folds in the window (Eliminate) Practical Examples - Fold Methods Vim offers six different methods for creating folds. Which one you choose depends on what you are editing and how much control you want.\nmanual - Manual Folding with zf The most direct method. You mark a region and fold it with zf. This works visually, with motions, or with brackets.\nVisually mark and fold\nMark the region with v (character-wise), V (line-wise), or Ctrl-v (block-wise)\nPress zf\nVim creates a fold from it. The text is preserved, only hidden.\nFolding with motions\nzfap folds a whole paragraph (a paragraph) zfG folds from the cursor to the end of the file (Go to end) zfgg folds from the beginning of the file to the cursor (go all the way up) Folding Between Brackets\nThe most elegant way for code. Place the cursor on an opening bracket such as { or [ and press zf%. Vim jumps to the matching closing bracket and folds everything in between.\nAn example in a JavaScript file:\nfunction calculateSum(a, b) { const result = a + b; console.log(result); return result; } With the cursor on the opening { and zf%, it then looks like this:\n+-- 4 lines: function calculateSum(a, b) { indent - Folding by Indentation The most practical method for code. Vim automatically creates folds based on the indentation depth.\n:set foldmethod=indent Each level of indentation becomes a fold level. When opening a file with foldlevel=1, all functions are collapsed and you see only the structure at a glance.\nmarker - Folding by Markers You place special markers in the text that Vim recognizes as fold boundaries. The default markers are {{{ and }}}.\n:set foldmethod=marker An example:\n\u0026lt;!-- {{{ --\u0026gt; \u0026lt;div class=\u0026#34;container\u0026#34;\u0026gt; \u0026lt;p\u0026gt;First line\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;Second line\u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;!-- }}} --\u0026gt; The text between the markers becomes a fold. The advantage: The fold is preserved when the file is opened again. The disadvantage: You are modifying the file itself.\nsyntax - Folding by Syntax Vim uses the existing syntax rules to create folds. For most programming languages, these rules are already present.\n:set foldmethod=syntax For Ruby code, for example, all methods are automatically folded. You do not need to configure anything further.\nexpr - Folding by Expression The most flexible method. You define an expression that decides for each line whether it is folded.\n:set foldmethod=expr :set foldexpr=getline(v:lnum)=~\u0026#39;ERROR\u0026#39; This example folds all lines that do not contain the word ERROR. Ideal for log files, to leave only the errors visible.\ndiff - Folding by diff When you compare two versions of a file, unchanged regions are automatically folded. This method is usually activated automatically when you use vimdiff or :diffthis.\nPractical Application For code files, I usually use foldmethod=indent in combination with foldlevel=1. Then, when opening a file, all functions are collapsed and I see only the structure at a glance. With za I then expand the function.\nFor longer text documents, foldmethod=manual is often better. Then I can decide for myself which paragraphs I collapse.\nAnd if the folds ever become too much, zR or zn helps to open everything again.\nSources Vim is available as open-source software.\nWebsite: https://www.vim.org/ Vim Documentation Folding: https://vimhelp.org/fold.txt.html Vim User Manual Folding: https://vimhelp.org/usr_28.txt.html Vim Folding Wiki: https://vim.fandom.com/wiki/Folding Deleting Old Views - Vimscript Here is a Vimscript that finds and deletes orphaned view files, along with instructions for integrating it.\nThe script uses the functions readdir() to list the view folder and delete() to delete the files. The reversal of the naming convention (=+ to /) is the core of the check.\nThe Reversal of the Naming Convention This is the crucial point. Vim does not save view files under the original name, but encodes the complete path into the file name. Every slash (/) is replaced by the string =+.\nAn example. The original file /home/user/test.txt becomes a view file with the name =+home=+user=+test.txt in the view folder.\n\u0026#34; Function to clean up orphaned view files function! s:CleanOrphanedViews() \u0026#34; Determine the view directory (default: ~/.vim/view) let view_dir = expand(\u0026amp;viewdir) \u0026#34; Check whether the view directory exists if !isdirectory(view_dir) echohl WarningMsg echo \u0026#34;View directory not found: \u0026#34; . view_dir echohl None return endif let orphaned_count = 0 \u0026#34; List all files in the view directory for view_file in readdir(view_dir) let view_path = view_dir . \u0026#39;/\u0026#39; . view_file \u0026#34; Only process files (no subfolders) if !filereadable(view_path) continue endif \u0026#34; Convert the file name back into a path \u0026#34; Vim replaces \u0026#39;/\u0026#39; with \u0026#39;=+\u0026#39; in the file name let original_path = substitute(view_file, \u0026#39;=+\u0026#39;, \u0026#39;/\u0026#39;, \u0026#39;g\u0026#39;) \u0026#34; Check whether the original file still exists \u0026#34; filereadable() is more robust than glob() with permission problems if !filereadable(original_path) \u0026#34; File no longer exists -\u0026gt; delete view if delete(view_path) == 0 let orphaned_count += 1 echom \u0026#34;Orphaned view deleted: \u0026#34; . view_file else echohl WarningMsg echom \u0026#34;Error deleting: \u0026#34; . view_file echohl None endif endif endfor \u0026#34; Output summary if orphaned_count \u0026gt; 0 echom \u0026#34;Cleanup complete: \u0026#34; . orphaned_count . \u0026#34; orphaned view(s) deleted.\u0026#34; else echom \u0026#34;No orphaned view files found.\u0026#34; endif endfunction \u0026#34; Define command to call the function command! CleanViews call s:CleanOrphanedViews() How the Script Works Determine directory: The script reads the global option \u0026amp;viewdir, which by default points to ~/.vim/view (Unix) or $VIM/vimfiles/view (Windows). List files: With readdir(), all entries in the view folder are read. Reconstruct path: The crucial step. Vim encodes the original path in the view file name by replacing every slash (/) with the string =+. The substitute() function reverses this replacement. Existence check: filereadable() checks whether the original file reconstructed in this way still exists and is readable. This is more robust than a pure glob() check if permissions are involved. Deleting: If the original file is missing, the view file is removed with the built-in delete() function. Integrating into the Vim Configuration You have two options for using the script permanently.\nOption 1: Insert directly into .vimrc\nCopy the entire code block into your ~/.vimrc (or ~/.config/nvim/init.vim). After saving and restarting Vim, you can use the command :CleanViews.\nOption 2: As a separate plugin (recommended for tidiness)\nCreate a file named cleanviews.vim in the directory ~/.vim/plugin/. Insert the code there. Vim automatically loads files in ~/.vim/plugin/ at startup. In both cases, the command :CleanViews is available. After running, it outputs a message stating how many orphaned view files were deleted.\n","permalink":"https://hyperblog.de/hoergen/en/posts/2026/2026-09-21-vim-folds/","summary":"\u003ch2 id=\"what-folds-actually-are\"\u003eWhat Folds Actually Are\u003c/h2\u003e\n\u003cp\u003eIn Vim, folds are collapsible and expandable regions in a text document. You can use them to collapse entire paragraphs, functions, or code blocks so that only the first line remains visible. This makes navigating large files considerably easier.\u003c/p\u003e\n\u003cp\u003eThe mnemonic for the command is simple. All fold commands begin with \u003ccode\u003ez\u003c/code\u003e, because the \u003ccode\u003ez\u003c/code\u003e looks like a folded piece of paper viewed from the side.\u003c/p\u003e","title":"Vim - Folds with z - simply explained"},{"content":" How can digital content be financed in the year 2026 without everything running through advertising or subscriptions?\nMicropayment, i.e., the payment of tiny amounts, sounds like a logical solution. In Germany, there are even still - and again - providers for it.\nWhy the Classic Model Never Caught On The classic model nevertheless never really caught on. The reason is practical. If a card payment already costs a fixed fee plus a percentage share, then a transaction of ten cents simply is not worth it. Micropayment therefore turned early on to bank transfers and direct debits, which are cheaper to process.\nThe Shift - When Machines Become Customers What is changing right now is the target group. Instead of people paying five cents for an article, it is increasingly AI agents that pay in the background for API calls, data retrievals, or cloud resources.\nThe x402 Protocol The technical foundation for this is a protocol called x402, which was originally developed by Coinbase and is now managed by the Linux Foundation. The name is derived from the HTTP status code 402, which stands for Payment Required.\nUntil now, this code was unused in practice. x402 brings it to life. The process is simple. A server responds to an unpaid request with status 402, the agent authorizes a payment, and the request is repeated.\nBy August 2026, around 75 million transactions with a volume of about 24 million US dollars had been processed via this protocol, which works out to about 32 cents per transaction.\nGerman Players Are Taking Part German players are taking part. Deutsche Telekom will act as a validator in the SphereNet network starting in 2027, checking identity, sanctions, and payment rules before a transaction is finally settled. Telekom brings its experience with validator infrastructure for other blockchain networks and already has 273 million mobile customers in over 50 countries.\nAllUnity, a stablecoin startup regulated by BaFin, has introduced a system called Agentic Payments, which is also based on x402. Companies can use it to accept transactions from AI agents, with the money landing directly in local bank accounts.\nMicropayment GmbH is a full-service payment provider founded in Berlin in 2005 that enables merchants to technically process online payments, with a focus on account- and bank-based payment methods such as SEPA transfer, SEPA direct debit, and open banking transfers (A2A), supplemented by credit cards, PayPal, and Paysafecard.\nAnd since August 2026, Stripe has also been enabling German companies to accept stablecoin payments from AI agents, though only on request. Amounts from one cent are possible.\nThe Regulatory Dimension Legally, the matter is probably not yet conclusively settled. German lawyers are discussing whether AI agents that initiate payments fall under the Payment Services Supervision Act.\nThe core question is whether the provider behind the agent counts as a payment initiator or money transfer service provider and therefore needs a license.\nThe commercial agent exemption in the ZAG is interesting. If an AI shopping assistant acts exclusively in the interest of the user, it could fall under this exemption and would not be subject to licensing.\nWhether this will be seen that way in practice is still open. For already licensed payment institutions, this opens up new business fields. They can use their license to enter the agentic payment business themselves or offer their regulatory infrastructure to AI startups.\nConclusion Whether this will catch on remains to be seen. But the development is concrete and well financed. And who knows, perhaps the next customer of a German company will someday not be a human, but an agent that queries an API for a fraction of a cent.\nSources Linux Foundation x402 Foundation Launch: https://x402.org/linux-foundation-announces-operational-launch-of-x402-foundation-to-standardize-internet-native-payments-for-ai-agents-and-applications/ Solana x402 Webinar Recap: https://solana.com/ja/news/webinar-recap-agentic-payments Deutsche Telekom SphereNet (BeInCrypto): https://de.beincrypto.com/deutsche-telekom-spherenet-ai-agent-zahlungen/ AllUnity Agentic Payments (ChainCatcher): https://www.chaincatcher.com/en/article/2266039 Stripe Agentic Commerce (etailment): https://www.etailment.de/magazin/sichtbar-im-agenten-gekauft-im-eigenen-shop-was-haendler-jetzt-einrichten-koennen ZAG and Agentic Payments (FIN LAW): https://fin-law.de/2026/09/07/einkaufsbudget-fuer-den-ki-agenten-sind-agentic-payments-erlaubnispflichtig-nach-dem-zag/ Micropayment Product Page: https://www.micropayment.de/partner/interview/ Micropayment Pay by Bank (IT Finanzmagazin): https://www.it-finanzmagazin.de/tag/micropayment/ ","permalink":"https://hyperblog.de/hoergen/en/posts/2026/2026-09-19-micropayment/","summary":"\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"http://www.picpedia.org/clipboard/images/payment.jpg\"\n         alt=\"Micropayment Germany\"/\u003e \n\u003c/figure\u003e\n\n\u003cp\u003eHow can digital content be financed in the year 2026 without everything running through advertising or subscriptions?\u003c/p\u003e\n\u003cp\u003eMicropayment, i.e., the payment of tiny amounts, sounds like a logical solution. In Germany, there are even still - and again - providers for it.\u003c/p\u003e\n\u003ch2 id=\"why-the-classic-model-never-caught-on\"\u003eWhy the Classic Model Never Caught On\u003c/h2\u003e\n\u003cp\u003eThe classic model nevertheless never really caught on. The reason is practical. If a card payment already costs a fixed fee plus a percentage share, then a transaction of ten cents simply is not worth it. Micropayment therefore turned early on to bank transfers and direct debits, which are cheaper to process.\u003c/p\u003e","title":"Micropayment in Germany - Of Tiny Amounts and Machines That Pay Themselves"},{"content":" Every now and then I scan documents with my smartphone. Invoices, receipts, notes, everything you accumulate in everyday life. I have been doing this for quite some time with the Android app OSS Document Scanner, an open-source alternative that works completely on the device. And the app is now also available as an iOS variant.\nThe concept is simple. The app turns your smartphone into a document scanner, with automatic edge detection, perspective correction, and OCR. Everything runs locally. No cloud, no account, no data sharing.\nWhat the App Can Do The feature list is extensive. You can scan documents with the camera or import images from the gallery. The app automatically detects the edges of the document and corrects the perspective.\nAfter scanning, you can improve the quality. There are filters for color or black-and-white, you can edit the crop, rearrange pages, rotate them, and adjust the brightness.\nFor organization, there are folders with colors, sorting functions, and the ability to rename documents.\nExport is done as PDF, JPEG, or PNG. You can share the files or save them directly on the device.\nAnd then there is OCR. The app uses Tesseract models that work offline. You download the language files once and can then extract text from your scans and add it to the PDF. The generated PDFs are then searchable.\nPrivacy and Offline Function This is the decisive point for me. OSS Document Scanner works completely offline. The app has no cloud lock-in. However, you can optionally set up synchronization via WebDAV, for example with your own Nextcloud instance.\nThe app is available on Android and iOS. It can be found in the Google Play Store, in the F-Droid Store, and on GitHub.\nHow the App Stands Out from the Competition Compared to CamScanner or similar apps, OSS Document Scanner is leaner and has better filters.\nThe app is released under the MIT license. The source code can be viewed on GitHub. The project is actively developed; the last version at the time of this article is from September 2026.\nConclusion OSS Document Scanner is a solid alternative for anyone who wants to scan documents without giving their data to someone else\u0026rsquo;s cloud. The app does what it is supposed to do and can optionally be synchronized with your own server via WebDAV. For me, that is exactly the right approach.\nDownload OSS Document Scanner is available as open-source software.\nWebsite: https://ossdocumentscanner.akylas.fr/ Getting started: https://ossdocumentscanner.akylas.fr/getting-started GitHub: https://github.com/ossappscollective/OSS-DocumentScanner IzzyOnDroid ","permalink":"https://hyperblog.de/hoergen/en/posts/2026/2026-09-19-oss-documentenscanner-pdf-ocr/","summary":"\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"https://raw.githubusercontent.com/ossappscollective/OSS-DocumentScanner/main/fastlane/metadata/com.akylas.documentscanner/android/en-US/images/featureGraphic.png\"\n         alt=\"OSS Document Scanner\"/\u003e \n\u003c/figure\u003e\n\n\u003cp\u003eEvery now and then I scan documents with my smartphone. Invoices, receipts, notes, everything you accumulate in everyday life. I have been doing this for quite some time with the Android app OSS Document Scanner, an open-source alternative that works completely on the device. And the app is now also available as an iOS variant.\u003c/p\u003e\n\u003cp\u003eThe concept is simple. The app turns your smartphone into a document scanner, with automatic edge detection, perspective correction, and OCR. Everything runs locally. No cloud, no account, no data sharing.\u003c/p\u003e","title":"OSS Document Scanner - Scan, PDF \u0026 OCR"},{"content":"I do run my own Friendica instance and have recently been dealing intensively with the deletion settings. Because nothing is more annoying than a database that keeps growing even though you have actually configured that old posts should be deleted. In this article I will show you how to set and check the deletion settings correctly, and what pitfalls there are.\nThe Right Order Before I go into the details, here is the order in which you should proceed.\nFirst you check the current settings in the database. Then you set the global values in local.config.php. After that you take care of the user-specific settings in the pconfig table. Finally you manually trigger the worker and check the result.\nChecking the Current Settings Before you change anything, you should know what is currently configured. There are three queries for this.\nGet the uid of the user ilsebilse. The result is the uid (number)\nSELECT `uid`, `username`, `nickname` FROM `user` WHERE `nickname` = \u0026#39;ilsebilse\u0026#39; OR `username` = \u0026#39;ilsebilse\u0026#39;; The global expiry value of the user is in the user table.\nSELECT uid, username, expire AS user_expire_days FROM `user` WHERE uid = 42; A value of 0 means that expiry is completely disabled for this user. Only with a value greater than 0 does the ExpirePosts worker process this user at all.\nThe user-specific deletion settings are in the pconfig table.\nSELECT uid, cat, k, v FROM `pconfig` WHERE uid = 42 AND cat = \u0026#39;expire\u0026#39; ORDER BY k; The most important keys are items, starred, notes, photos, and network_only. The photos key is set to false by default. This means that photos normally do not expire.\nThe global limit for external posts is in the config table.\nSELECT `k`, `v` FROM `config` WHERE `cat` = \u0026#39;system\u0026#39; AND `k` = \u0026#39;expire_limit\u0026#39;; A value of 0 means that this function is disabled.\nSetting Global Settings in local.config.php The local.config.php is the central configuration file for instance-specific overrides. It is loaded after the default values and overrides them.\nThe most important settings for expiry control are these.\n\u0026lt;?php return [ \u0026#39;system\u0026#39; =\u0026gt; [ ... ... \u0026#39;expire_limit\u0026#39; =\u0026gt; 365, \u0026#39;dbclean\u0026#39; =\u0026gt; true, \u0026#39;dbclean-expire-limit\u0026#39; =\u0026gt; 10000, \u0026#39;dbclean-expire-days\u0026#39; =\u0026gt; 90, \u0026#39;dbclean_expire_conversation\u0026#39; =\u0026gt; 90, \u0026#39;optimize_tables\u0026#39; =\u0026gt; true, ... ... ], ]; The expire_limit value controls how many days external posts are retained. A value of 0 disables the function.\nThe dbclean value activates the automatic database cleanup. Without this value, the ExpirePosts worker does not run automatically on every cron run.\nThe dbclean-expire-limit value limits how many records are deleted per cron run. The default value is 1000. On active instances this is too low. The database grows faster than the worker can clean up. For medium to large instances I recommend a value between 5000 and 10000.\nThe dbclean-expire-days value controls how many days remote posts are retained.\nThe optimize_tables value activates the automatic optimization of frequently used tables. But caution. This option only optimizes a subset of the tables. Other tables with deleted data are not automatically optimized. The storage space is only freed after manual optimization.\nSetting User-Specific Settings Now you take care of the user-specific settings. First you set the expiry duration in days.\nUPDATE `user` SET `expire` = 365 WHERE uid = 42; Then you activate the desired content types in the pconfig table.\nINSERT INTO `pconfig` (`uid`, `cat`, `k`, `v`) VALUES (42, \u0026#39;expire\u0026#39;, \u0026#39;items\u0026#39;, \u0026#39;1\u0026#39;), (42, \u0026#39;expire\u0026#39;, \u0026#39;starred\u0026#39;, \u0026#39;1\u0026#39;), (42, \u0026#39;expire\u0026#39;, \u0026#39;notes\u0026#39;, \u0026#39;1\u0026#39;), (42, \u0026#39;expire\u0026#39;, \u0026#39;photos\u0026#39;, \u0026#39;1\u0026#39;), (42, \u0026#39;expire\u0026#39;, \u0026#39;network_only\u0026#39;, \u0026#39;0\u0026#39;) ON DUPLICATE KEY UPDATE `v` = VALUES(`v`); A value of 1 activates the respective type, a value of 0 deactivates it. If you set photos to 1, photos will also expire.\nChecking and Triggering the Worker After you have set all settings, you check them with a combined query.\nSELECT u.uid, u.username, u.expire AS user_expire_days, MAX(CASE WHEN p.k = \u0026#39;items\u0026#39; THEN p.v END) AS expire_items, MAX(CASE WHEN p.k = \u0026#39;starred\u0026#39; THEN p.v END) AS expire_starred, MAX(CASE WHEN p.k = \u0026#39;notes\u0026#39; THEN p.v END) AS expire_notes, MAX(CASE WHEN p.k = \u0026#39;photos\u0026#39; THEN p.v END) AS expire_photos, MAX(CASE WHEN p.k = \u0026#39;network_only\u0026#39; THEN p.v END) AS expire_network_only FROM `user` u LEFT JOIN `pconfig` p ON u.uid = p.uid AND p.cat = \u0026#39;expire\u0026#39; WHERE u.uid = 42 GROUP BY u.uid; Then you manually trigger the worker in the root directory of Friendica. It normally runs at night between 00:00 and 03:00.\nphp ./bin/console.php worker The ExpirePosts worker is part of the worker chain and is executed on every cron run.\nSummary: The Pitfalls There was a known bug in Friendica. The user-specific deletion settings had no effect in many versions. Posts are not deleted despite correct values. This was a bug in Friendica and not a configuration problem. - The expiry settings of posts have no effect · Issue #14413 · friendica/friendica\nThe default value for dbclean-expire-limit is 1000. On active instances this value is too low. The database grows faster than the worker can clean up. The impression arises that the cleanup is not working.\nDeleting posts does not automatically free the storage space in the database. For that, the table must be optimized. With large databases, the cleanup can take so long that tables are locked and the instance no longer responds. A manual optimize in maintenance mode is the better option in such cases.\nmysqlcheck -u root -p --optimize your_database_name # with password prompt The values in local.config.php override the default values from static/defaults.config.php. However, they are themselves overridden by values in the config database table.\nDownload Friendica is available as open-source software.\nWebsite: https://friendi.ca/ GitHub: https://github.com/friendica/friendica Documentation: https://wiki.friendi.ca/ ","permalink":"https://hyperblog.de/hoergen/en/posts/2026/2026-09-17-friendica-loeschung-posts-photos/","summary":"\u003cp\u003eI do run my own Friendica instance and have recently been dealing intensively with the deletion settings. Because nothing is more annoying than a database that keeps growing even though you have actually configured that old posts should be deleted. In this article I will show you how to set and check the deletion settings correctly, and what pitfalls there are.\u003c/p\u003e\n\u003ch2 id=\"the-right-order\"\u003eThe Right Order\u003c/h2\u003e\n\u003cp\u003eBefore I go into the details, here is the order in which you should proceed.\u003c/p\u003e","title":"Friendica - Configuring Deletion Settings Correctly"},{"content":"I had noticed a strange problem in Friendica. For one user, the groups widget was no longer displayed in the sidebar. Even though the user followed several groups, those groups had current posts, and groups was included in the widgetorder setting.\nOther users on the same instance saw the widget correctly. Only this one user did not.\nThe Cause After some searching, I found the culprit. It was due to a custom widgetorder setting in the pconfig table. In the RC version, this custom order is not correctly merged with the groups widget. The widget remains invisible, even though all the data is present and the configuration is actually correct.\nThe solution is simple. I have to delete the widgetorder entry. Friendica then falls back to the default widget order, and the groups widget appears again.\nThe Instructions for the Fix The solution steps using the example of a user named HansFranz with uid 42.\nStep 1 Identify the Affected User First you need to find out the uid of the affected user in the database.\nSELECT `uid`, `username`, `nickname` FROM `user` WHERE `nickname` = \u0026#39;HansFranz\u0026#39; OR `username` = \u0026#39;HansFranz\u0026#39;; In my example, the uid is 42.\nStep 2 Check the Current widgetorder Entry Now you look at what is currently in the widgetorder.\nSELECT `v` AS widgetorder FROM `pconfig` WHERE `uid` = 42 AND `cat` = \u0026#39;feature\u0026#39; AND `k` = \u0026#39;widgetorder\u0026#39;; In the problem case, the output looks like this.\n[\u0026#34;circles\u0026#34;,\u0026#34;groups\u0026#34;,\u0026#34;pages\u0026#34;,\u0026#34;channels\u0026#34;,\u0026#34;archive\u0026#34;,\u0026#34;networks\u0026#34;,\u0026#34;accounts\u0026#34;,\u0026#34;searches\u0026#34;,\u0026#34;folders\u0026#34;,\u0026#34;nosharer\u0026#34;,\u0026#34;trending_tags\u0026#34;] Although groups is included, the widget is not displayed. That is the bug.\nStep 3 Delete the widgetorder Entry Now you delete the entry.\nDELETE FROM `pconfig` WHERE `uid` = 42 AND `cat` = \u0026#39;feature\u0026#39; AND `k` = \u0026#39;widgetorder\u0026#39;; Step 4 Clear the Cache After that, you clear the cache on the server with:\n./bin/console cache clear Step 5 Verify That the Entry Has Been Removed To check, query it once more.\nSELECT * FROM `pconfig` WHERE `uid` = 42 AND `cat` = \u0026#39;feature\u0026#39; AND `k` = \u0026#39;widgetorder\u0026#39;; The expected result is an empty result set with 0 rows.\nStep 6 Test Now you log out as HansFranz and log in again. The groups widget should now appear in the sidebar. Just like with users without a custom widgetorder.\nSummary The affected version in my case was Friendica 2026.08-rc. The symptom is an invisible groups widget, even though groups is present in the widgetorder and groups exist. The cause lies in the custom widgetorder in the pconfig table, which is processed incorrectly in the RC version. The solution is to delete the widgetorder entry. Then clear the cache and log in again.\nDownload Friendica is one of the greatest projects of the Fediverse and is available as open-source software.\nWebsite: https://friendi.ca/ GitHub: https://github.com/friendica/friendica Documentation: https://wiki.friendi.ca/ ","permalink":"https://hyperblog.de/hoergen/en/posts/2026/2026-09-17-friendica-noforum-group-widget/","summary":"\u003cp\u003eI had noticed a strange problem in Friendica. For one user, the groups widget was no longer displayed in the sidebar. Even though the user followed several groups, those groups had current posts, and groups was included in the widgetorder setting.\u003c/p\u003e\n\u003cp\u003eOther users on the same instance saw the widget correctly. Only this one user did not.\u003c/p\u003e\n\u003ch2 id=\"the-cause\"\u003eThe Cause\u003c/h2\u003e\n\u003cp\u003eAfter some searching, I found the culprit. It was due to a custom \u003ccode\u003ewidgetorder\u003c/code\u003e setting in the \u003ccode\u003epconfig\u003c/code\u003e table. In the RC version, this custom order is not correctly merged with the groups widget. The widget remains invisible, even though all the data is present and the configuration is actually correct.\u003c/p\u003e","title":"Friendica - Solution for Invisible Groups Widget"},{"content":"I regularly copy files onto USB sticks or SD cards that are formatted with FAT32. And every time there is trouble with file names that contain characters FAT32 does not allow. Colons, question marks, asterisks, everything that is completely normal under Linux becomes a problem on FAT32.\nSo I wrote myself a Bash script that takes care of the work. It searches the current folder, replaces all invalid characters, and also takes care of leading or trailing spaces and dots. Collisions are avoided by appending a counter.\nWhat the Script Does The script works in several steps. First, all files in the current folder are iterated through. For each file, the name is checked and cleaned up.\nThe characters forbidden by FAT32 are the backslash, the slash, the colon, the asterisk, the question mark, the quotation marks, the angle brackets, and the pipe. All of these characters are replaced by an underscore.\nIn addition, control characters are removed, i.e., characters with the codes 0x00 to 0x1F. They rarely appear, but when they do, they cause problems.\nThen leading and trailing spaces and dots are removed. FAT32 does not like that, even if it is not a problem on other file systems.\nIf the name is empty after that, an emergency name is assigned.\nAnd finally, it is checked whether a file with the new name already exists. If so, a counter is appended to avoid collisions.\nHow to Use It Save the script in the folder where you want to use it under a name such as fat32-rename.sh and start it\nbash fat32-rename.sh The script only works in the current folder. Subfolders are not searched. If you also want to clean up subfolders, you would have to combine it with find or write a recursive variant.\nWhat You Should Keep in Mind The script renames files. If you want to make sure that nothing goes wrong, you should make a backup beforehand or at least test what the script would do before you unleash it on important data.\nThe script does not overwrite files. If a file with the new name already exists, a counter is appended so that the new name is unique.\nThe script only processes files, not folders. If you also want to rename folders, you would have to adjust the condition [ -f \u0026quot;$f\u0026quot; ] accordingly.\nConclusion The script has already saved me a lot of time. Instead of renaming files individually, I simply let it run over the folder and afterwards I copy the files onto the FAT32 storage medium. This works reliably and I no longer have to worry about invalid characters.\nIf you frequently deal with FAT32, this is a small helper that quickly pays for itself.\nThe Script #!/usr/bin/env bash # Replaces FAT32-invalid characters in file names in the current folder # Author: hoergen # 15.09.2026 for f in *; do [ -f \u0026#34;$f\u0026#34; ] || continue # Replace FAT32-forbidden characters: # \\ / : * ? \u0026#34; \u0026lt; \u0026gt; | -\u0026gt; _ # Additionally remove: control characters (0x00-0x1F) at the beginning/end new=$(echo \u0026#34;$f\u0026#34; \\ | tr \u0026#39;\\\\/:*?\u0026#34;\u0026lt;\u0026gt;|\u0026#39; \u0026#39;_________\u0026#39; \\ | tr -d \u0026#39;\\000-\\037\u0026#39;) # Remove leading/trailing spaces and dots # (FAT32 does not like that) new=$(echo \u0026#34;$new\u0026#34; | sed -e \u0026#39;s/^[ .]*//\u0026#39; -e \u0026#39;s/[ .]*$//\u0026#39;) # Empty name -\u0026gt; emergency name [ -z \u0026#34;$new\u0026#34; ] \u0026amp;\u0026amp; new=\u0026#34;unnamed\u0026#34; if [ \u0026#34;$f\u0026#34; != \u0026#34;$new\u0026#34; ]; then # Avoid collisions if [ -e \u0026#34;$new\u0026#34; ]; then base=\u0026#34;${new%.*}\u0026#34; ext=\u0026#34;${new##*.}\u0026#34; [ \u0026#34;$base\u0026#34; = \u0026#34;$ext\u0026#34; ] \u0026amp;\u0026amp; ext=\u0026#34;\u0026#34; n=1 while [ -e \u0026#34;${base}_$n${ext:+.$ext}\u0026#34; ]; do n=$((n+1)) done new=\u0026#34;${base}_$n${ext:+.$ext}\u0026#34; fi mv -- \u0026#34;$f\u0026#34; \u0026#34;$new\u0026#34; echo \u0026#34;OK $f -\u0026gt; $new\u0026#34; fi done ","permalink":"https://hyperblog.de/hoergen/en/posts/2026/2026-09-15-dateien-fat32-kompatibel-umbenennen/","summary":"\u003cp\u003eI regularly copy files onto USB sticks or SD cards that are formatted with FAT32. And every time there is trouble with file names that contain characters FAT32 does not allow. Colons, question marks, asterisks, everything that is completely normal under Linux becomes a problem on FAT32.\u003c/p\u003e\n\u003cp\u003eSo I wrote myself a Bash script that takes care of the work. It searches the current folder, replaces all invalid characters, and also takes care of leading or trailing spaces and dots. Collisions are avoided by appending a counter.\u003c/p\u003e","title":"Renaming Files to Be FAT32-Compatible - Bash Script"},{"content":"tmuxp - a session manager for tmux that loads, freezes, and converts sessions via declarative YAML or JSON files.\nThe project is based on libtmux and is actively developed on GitHub. The idea is simple. Instead of manually opening five windows every morning and typing in commands, you define your working environment once as a file and load it with a single command.\nWhat tmuxp Does tmuxp manages tmux sessions based on configuration files. In them, you describe which windows and panes should be opened and which commands should be executed in them. When loading, tmuxp builds the session exactly as you defined it.\nThis works with YAML and JSON. tmuxp also supports the formats of tmuxinator and teamocil, which makes switching easier if you already use one of these solutions.\nInstallation tmuxp can be installed in various ways. Depending on the system and preference, there are several options.\nOn Debian and Ubuntu it runs via apt.\nsudo apt install tmuxp For Manjaro\nsudo pamac install tmuxp Other installation methods are described on GitHub.\nLoading a Session The heart of tmuxp is the load command. You define a session in a YAML file and load it with it.\nA simple example looks like this.\n# yaml file session_name: my-project windows: - window_name: editor panes: - shell_command: - vim - shell_command: - git status You save this file as my-project.yaml and then load it.\ntmuxp load my-project.yaml Explanation:\ntmuxp then builds a session with the name my-project. It contains a window named editor, which is split into two panes. In the first pane, vim runs in the second, git status is executed. More Complex Configuration tmuxp can do considerably more than just simple windows. You can define layouts, execute commands before all panes start, and equip multiple panes with different tasks.\nAn example with four panes and a predefined layout.\nsession_name: 4-pane-split windows: - window_name: dev window layout: tiled shell_command_before: - cd ~/ panes: - shell_command: - cd /var/log - ls -al | grep \\.log - echo second pane - echo third pane - echo fourth pane Explanation:\nThe parameter shell_command_before executes a command in all panes before the actual commands start. In this case, each pane first switches to the home directory. The parameter layout determines how the panes are arranged. tiled means that all panes are distributed evenly. Size Specifications The layout Option The central option for this is layout. You can either use a predefined name or a specific layout string that tmux itself generates.\nPredefined layouts are easy to use but give you less control:\nwindows: - window_name: dev layout: main-horizontal panes: - vim - git status Other common names are tiled, even-horizontal, and even-vertical.\nSpecific Sizes and Positions If you want to define exactly how the panes are arranged, you must use the layout string from tmux. This string is an encoded description of the division and size of each cell.\nManually build a tmux session with the desired pane divisions. Query the current layout: tmux list-windows Copy the layout string (the long code after layout:) into your tmuxp YAML file. An example of such a string looks like this:\nlayout: 382a,80x60,0,0[80x10,0,0,0,80x10,0,11,1,80x10,0,22,2,80x8,0,33,3] The numbers encode the width x height and position of each pane. This is precise, but not particularly readable.\nAlternative % for Simple Adjustments If you only want to adjust the height of the main pane in layouts such as main-horizontal or main-vertical, you can use the main-pane-height option. This can be specified in lines or, since tmuxp 1.46.0, also in percent.\nwindows: - window_name: rust-study layout: main-horizontal options: main-pane-height: 67% panes: - vim - cargo run This is often the more practical way if you only want to control the size ratio between the main and secondary panes.\nProjects and Configuration Directories tmuxp searches for configurations in various directories. If you place a file named .tmuxp.yaml or .tmuxp.json in a project folder, you can load it directly via the folder path.\ntmuxp load path/to/my/project/ For user-wide configurations, tmuxp automatically searches several directories. This is practical if you want to load your sessions from anywhere without specifying the full path.\n$TMUXP_CONFIGDIR, if set $XDG_CONFIG_HOME, usually $HOME/.config/tmuxp/ $HOME/.tmuxp/ If your configuration is located under ~/.config/tmuxp/mysession.yaml, the following command is sufficient.\ntmuxp load mysession You can also load multiple sessions at the same time.\ntmuxp load mysession ./another/project/ Or give the session its own name.\ntmuxp load -s session_name ./mysession.yaml Freezing Sessions (Saving) Sometimes you have already built a tmux session and want to save it as a configuration. For this there is the freeze command.\ntmuxp freeze session-name tmuxp then creates a YAML or JSON file that describes the current state of the session. Layout, pane paths, window names, and session names are adopted. This is practical if you have built a session spontaneously and want to reproduce it later.\nThe tmuxp freeze command does not save the file in a fixed directory by default. Instead, you are asked where the file should be saved when you run it.\nWhere to Save When you run tmuxp freeze session-name, tmuxp offers to save the state as a .yaml or .json file.\nWhen running tmuxp freeze, you are interactively asked where the file should be saved. You can then specify the location or confirm a suggested path.\nStorage Locations for Configurations If you want to conveniently load your frozen session by name later, you should place it in one of the following directories:\n~/.tmuxp/ – the classic directory ~/.config/tmuxp/ – the XDG standard path Project-local – as .tmuxp.yaml or .tmuxp.json in the project folder Specifying Your Own Path You can also specify the storage location directly with the -o or --save-to parameter.\ntmuxp freeze my-session -o /path/to/file.yaml Converting Between Formats If you want to convert a configuration from YAML to JSON or vice versa, you can do this with the convert command.\ntmuxp convert filename tmuxp shows you the new file and asks for confirmation. If you want to confirm the prompt automatically, you can use the -y parameter.\ntmuxp convert -y filename The tmuxp Shell Since version 1.6.0 there is the command tmuxp shell. This starts a Python console that is preloaded with the current server, session, and window as libtmux objects.\ntmuxp shell This is useful if you want to script or automate tmux sessions.\nPlugins and Extensions tmuxp has a plugin system with which you can add your own behavior. This is interesting if you have special requirements that go beyond the standard functions.\nA pre-load hook allows you to execute custom scripts before tmux is loaded. This can be used, for example, to install project dependencies.\nDebugging If something goes wrong when loading a session, you can write the output to a log file.\ntmuxp load --log-file \u0026lt;log-file-name\u0026gt; . For bug reports there is the command tmuxp debug-info, which collects system information.\ntmuxp debug-info Load in the Background If you want to load a session without attaching to it directly, you can use the -d parameter.\ntmuxp load -d mysession.yaml This is practical if you want to prepare multiple sessions and decide later which one to use.\nConclusion tmuxp is a tool that makes tmux considerably more pleasant for me. Instead of building the same session by hand every time, I define it once and load it with a single command. Or copy it to multiple machines so that I only have to define it once. This saves time and reduces errors.\nI find the ability to freeze sessions particularly practical. If I have spontaneously built a session that I need more often, I simply save it as a configuration and have it ready immediately the next time.\nThanks to Markus from the Fediverse https://social.row-social.de/@markus, who brought it to my attention!\nSources and Download tmuxp is available as open-source software under the MIT license.\nWebsite: https://tmuxp.git-pull.com/ GitHub: https://github.com/tmux-python/tmuxp Documentation: https://tmuxp.git-pull.com/ PyPI: https://pypi.org/project/tmuxp/ ","permalink":"https://hyperblog.de/hoergen/en/posts/2026/2026-09-13-tmuxp-sessions-definieren-verwalten/","summary":"\u003cp\u003etmuxp - a session manager for tmux that loads, freezes, and converts sessions via declarative YAML or JSON files.\u003c/p\u003e\n\u003cp\u003eThe project is based on libtmux and is actively developed on GitHub. The idea is simple. Instead of manually opening five windows every morning and typing in commands, you define your working environment once as a file and load it with a single command.\u003c/p\u003e\n\u003ch2 id=\"what-tmuxp-does\"\u003eWhat tmuxp Does\u003c/h2\u003e\n\u003cp\u003etmuxp manages tmux sessions based on configuration files. In them, you describe which windows and panes should be opened and which commands should be executed in them. When loading, tmuxp builds the session exactly as you defined it.\u003c/p\u003e","title":"tmuxp - Defining \u0026 Managing tmux Sessions"},{"content":"How to Train as an Interviewer to Disenchant AfD Representatives 1. Basic Attitude: Clearly Define the Journalistic Role The most important thing first: An interview is not an argument and not a debate. The interviewer is not the opponent, but the representative of the audience. This role must be trained – because AfD representatives systematically try to push journalists into the role of a fellow debater (Strategy 8: Counter-questions).\nTraining:\nRole-plays in which the interviewer deliberately does NOT respond to counter-questions Exercise: \u0026ldquo;I ask the questions, you answer\u0026rdquo; – say this sentence calmly and firmly Self-observation: When do I become a debater? When do I defend myself instead of asking follow-up questions? 2. Tools: Training the Disenchantment Strategies 2.1 Staying on Topic (Strategy 1) This is the most important skill. AfD representatives like to jump from topic to topic – from unemployment to security, from security to migration, from migration to pensions. Whoever does not pay attention loses the thread.\nTraining:\nPrepare the interview with a clear chain of questions: One question → answer → follow-up → answer → follow-up. Do not let go. Exercise: \u0026ldquo;The Five Whys\u0026rdquo; – with every evasive answer, ask again until a concrete answer comes. Example: An AfD representative is asked about the unemployment rate in 1995. He evades. Better would be: \u0026ldquo;You did not answer my question. What was good about 20 percent unemployment?\u0026rdquo; 2.2 Setting Facts Against Emotions (Strategy 2) Numbers are the sharpest sword against \u0026ldquo;lived reality\u0026rdquo; rhetoric. But: The emotional level must be taken seriously, otherwise the interviewer appears cold and arrogant.\nTraining:\nFact-check before every interview: Which numbers are relevant? Where do they contradict the claim? Exercise: Formulate facts in such a way that they do not sound preachy: \u0026ldquo;You say people feel less safe. The number of recorded crimes has almost halved since 1995. How do you explain this contradiction?\u0026rdquo; Important: Do not fall into an Excel spreadsheet yourself. The number is the entry point, not the end. 2.3 Marking Evasion (Strategy 3) This is the simplest and most effective technique. AfD representatives constantly evade. When the interviewer names this, it becomes visible to the audience.\nTraining:\nPractice standard formulations: \u0026ldquo;You did not answer my question.\u0026rdquo; \u0026ldquo;I\u0026rsquo;ll ask again: [repeat question].\u0026rdquo; \u0026ldquo;That was a counter-question. I ask the questions.\u0026rdquo; \u0026ldquo;You are evading. Why?\u0026rdquo; Exercise: Analyze interview recordings. Where was evasion? How could it have been marked? Important: Stay calm and objective. Not accusatory, but stating. 2.4 Not Getting Entangled in Discussions (Strategy 4) AfD representatives want to discuss, not answer. They want the journalist as an opponent in order to stage themselves as victims. Whoever lets themselves get entangled plays into their hands.\nTraining:\nRole-play: The interview partner asks counter-questions. The interviewer does NOT respond to them, but repeats his question. Exercise: \u0026ldquo;Thank you for the counter-question. I ask the questions. My question was: [repetition].\u0026rdquo; Self-control: Do I notice when I start to argue? If yes: Stop. Back to the question. 2.5 Conducting Fundamental Debates (Strategy 5) Do not get lost in details, but ask fundamental questions. With an AfD representative, that would be: \u0026ldquo;You want the population level of 1990. How is that supposed to work democratically?\u0026rdquo; or \u0026ldquo;You want to replace 200,000 workers through births. How is that supposed to work in 10 years?\u0026rdquo;\nTraining:\nPrepare a catalog of questions: What are the fundamental contradictions in the program? Exercise: Formulate the three most important fundamental questions and keep returning to them in the interview. Example: \u0026ldquo;You say you want less migration. At the same time, 200,000 workers are missing. How does that fit together?\u0026rdquo; 2.6 Making an Ideological Classification (Strategy 6) Terms such as \u0026ldquo;remigration,\u0026rdquo; \u0026ldquo;globalist elites,\u0026rdquo; \u0026ldquo;German culture\u0026rdquo; are not neutral wordings, but a worldview. Whoever treats them merely as a language style normalizes them.\nTraining:\nBackground knowledge: What does \u0026ldquo;remigration\u0026rdquo; really mean? What is the \u0026ldquo;New Right\u0026rdquo;? What is the \u0026ldquo;Great Replacement\u0026rdquo;? Exercise: Decode terms. \u0026ldquo;You say remigration. That means people with a migration background should leave the country. Is that correct?\u0026rdquo; Important: Do not interpret, but ask: \u0026ldquo;What exactly do you mean by that?\u0026rdquo; 2.7 More Resources for AfD Interviews (Strategy 7) AfD interviews are not normal interviews. They require more preparation, more time, more staff.\nTraining:\nPreparation team: Someone does the fact-check, someone analyzes previous interviews, someone prepares questions. Time: At least twice as much preparation time as for other interviews. Follow-up: Evaluate the interview. What worked? What did not? 2.8 Pointing Out Historical Parallels (Strategy 8) Klemperer\u0026rsquo;s \u0026ldquo;LTI\u0026rdquo; (Lingua Tertii Imperii) is a good reference. He analyzed how Nazi language worked: emotionalization, repetition, enemy images, victim myth.\nTraining:\nReading: Victor Klemperer, LTI. Notebook of a Philologist. Exercise: Analyze current AfD rhetoric with Klemperer\u0026rsquo;s categories. In the interview: Do not draw direct Nazi comparisons (that is a no-go), but recognize the mechanisms and counter them. 2.9 Media Reflection (Strategy 9) Media have contributed to normalization through the attention economy and \u0026ldquo;fascination with evil.\u0026rdquo; This must be reflected upon.\nTraining:\nSelf-critical question: Why are we interviewing this person? Because they are relevant? Or because they bring ratings? Exercise: Discussion in the team: How can we report without normalizing? Important: Do not broadcast every provocation. Do not leave every lie uncommented. 2.10 Values Level Instead of Details (Strategy 10) With topics such as Ukraine, defense, asylum law, it is not about numbers, but about values. These values must be conveyed.\nTraining:\nFormulate questions that address values: \u0026ldquo;You say we should not send money to Ukraine. At the same time you say Germany should be safe. How does that fit together?\u0026rdquo; Exercise: Identify value conflicts in the AfD program and address them in the interview. Important: Do not moralize, but ask: \u0026ldquo;What does that mean for the people in Ukraine?\u0026rdquo; 3. Concrete Exercises for Everyday Use 3.1 Interview Analysis Take an interview with an AfD representative and analyze:\nWhere does he evade? Where does he contradict himself? Where does he delegitimize statistics? Where does he ask counter-questions? Where should the interviewer have asked follow-up questions? 3.2 Role-Play Simulate the interview with a colleague who plays the AfD representative.\nThe \u0026ldquo;AfD representative\u0026rdquo; applies all strategies: level switching, counter-questions, \u0026ldquo;lived reality,\u0026rdquo; self-trivialization. The interviewer practices: staying on topic, marking evasion, not getting entangled. Afterwards: Feedback. What worked? What did not? 3.3 Creating a Question Catalog Create a question catalog for every AfD interview with:\n3 fundamental questions 5 factual questions 5 follow-up questions for evasion 3 value questions 3.4 Fact-Check Training Practice finding and formulating facts quickly.\nWhere do I find statistics? (Destatis, BKA, IHK, Leibniz Institute) How do I formulate them briefly and understandably? How do I counter \u0026ldquo;That is theoretical\u0026rdquo;? 3.5 Self-Reflection After every interview:\nDid I maintain my role as a journalist? Did I let myself get entangled? Did I mark evasion? Did I stay on topic? What will I do better next time? 4. The Most Important Sentences for the Toolbox Situation Sentence Evasion \u0026ldquo;You did not answer my question.\u0026rdquo; Counter-question \u0026ldquo;I ask the questions. My question was: …\u0026rdquo; Level switching \u0026ldquo;That is a different topic. Back to my question.\u0026rdquo; \u0026ldquo;Lived reality\u0026rdquo; \u0026ldquo;You are talking about feelings. The numbers say something different. How do you explain that?\u0026rdquo; Statistic delegitimization \u0026ldquo;That is a statistic from the Federal Criminal Police Office. Why should it be wrong?\u0026rdquo; Contradiction \u0026ldquo;You just said X. Earlier you said Y. Which is it now?\u0026rdquo; Victim staging \u0026ldquo;You are here to answer questions. That is not a victim role, that is an interview.\u0026rdquo; Provocation \u0026ldquo;That is a strong claim. Can you substantiate it?\u0026rdquo; 5. Conclusion: Disenchantment Is Craftsmanship Disenchantment is not a question of courage or morality, but of craftsmanship. AfD representatives use predictable strategies. Whoever knows these strategies can counter them.\nThe three most important skills:\nStay on topic – do not let yourself be distracted. Mark evasion – make it visible when no answer comes. Know the facts – numbers, data, contradictions. The most important principle: Do not discuss, but ask. Do not moralize, but ask follow-up questions. Do not get entangled, but maintain the role.\nAnd: Practice makes perfect. No interviewer is born as a disenchanter. But anyone can learn it – with preparation, role-play, and self-reflection.\n6. All Strategies at a Glance Communication Strategies of AfD Representatives Feelgood right-wing populism: Packaging a radical agenda in good mood, enthusiasm, and a positive vision. Approachability and mateyness: Actively approaching people, asking questions, terms of endearment, bro-like/matey style. \u0026ldquo;Lived reality\u0026rdquo; as a combat term: Placing subjective/emotional perception above objective facts; delegitimizing statistics as out of touch with reality. Self-trivialization (according to Kubitschek): Reducing the emotional barrier to the ordinary citizen; whitewashing the völkisch program. Staging as a reconciler/bridge-builder: Approaching counter-demonstrators, listening to (letting them present) their perspective, filming oneself doing so; portraying others as dividers. Movement aesthetics: Professional PR teams, warm/bright visual language, Simson parades, staging of strength, departure, community, East German identity. Level switching: Generalizing on differentiated questions, calling for differentiation on generalized questions. Asking counter-questions: Not answering questions, but asking counter-questions in order to delegitimize journalists as questioners. Positive identification surface: Not only serving enemy images (\u0026ldquo;those up there\u0026rdquo;), but also positive feelings and sympathy. Emotional narratives: \u0026ldquo;Good old intact world,\u0026rdquo; \u0026ldquo;Germany but normal,\u0026rdquo; \u0026ldquo;We are taking our country back.\u0026rdquo; Strategies for Disenchantment Stay on topic: Do not jump from topic to topic, but persistently ask follow-up questions. Set facts against emotions: Numbers and statistics as a counterpole to subjective perception, but take the emotional level seriously and acknowledge it. Mark evasion: Clearly name it when someone does not answer questions or asks counter-questions. Do not get entangled in discussions: Journalists should maintain their role and not become fellow debaters. Conduct fundamental debates: Do not get lost in Excel spreadsheet details, but ask fundamental questions (EU, NATO, asylum law – yes or no?). Make an ideological classification: Decode terms such as \u0026ldquo;globalist elites,\u0026rdquo; \u0026ldquo;German culture,\u0026rdquo; \u0026ldquo;passport Germans\u0026rdquo; as a worldview, not treat them as mere wordings. More resources for AfD interviews: AfD is not a normal party; interviews require more preparation, time, and staff. Point out historical parallels: Use Klemperer\u0026rsquo;s analysis of Nazi language as a reference for current rhetoric. Media reflection: Ask whether media have contributed to normalization through the attention economy and \u0026ldquo;fascination with evil.\u0026rdquo; Values level instead of details: With topics such as Ukraine/defense, convey fundamental values and complexity, not just numbers. ","permalink":"https://hyperblog.de/hoergen/en/posts/2026/2026-09-13-wie-die-afd-entzaubert-wird/","summary":"\u003ch1 id=\"how-to-train-as-an-interviewer-to-disenchant-afd-representatives\"\u003eHow to Train as an Interviewer to Disenchant AfD Representatives\u003c/h1\u003e\n\u003ch2 id=\"1-basic-attitude-clearly-define-the-journalistic-role\"\u003e1. Basic Attitude: Clearly Define the Journalistic Role\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003eThe most important thing first:\u003c/strong\u003e An interview is not an argument and not a debate. The interviewer is not the opponent, but the representative of the audience. This role must be trained – because AfD representatives systematically try to push journalists into the role of a fellow debater (Strategy 8: Counter-questions).\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eTraining:\u003c/strong\u003e\u003c/p\u003e","title":"How to Train as an Interviewer to Disenchant AfD Representatives"}]