<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Manuel Bogner&#039;s Blog</title>
	<atom:link href="https://blog.mbo.dev/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.mbo.dev</link>
	<description>Solutions to everyday IT problems</description>
	<lastBuildDate>Fri, 11 Apr 2025 12:05:06 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://blog.mbo.dev/wp-content/uploads/2022/11/cropped-cropped-mbo-white_opt-32x32.png</url>
	<title>Manuel Bogner&#039;s Blog</title>
	<link>https://blog.mbo.dev</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Install Python 3.13 without GIL on Apple Silicone</title>
		<link>https://blog.mbo.dev/archives/2055</link>
		
		<dc:creator><![CDATA[Manuel Bogner]]></dc:creator>
		<pubDate>Fri, 11 Apr 2025 11:27:36 +0000</pubDate>
				<category><![CDATA[Development]]></category>
		<guid isPermaLink="false">https://blog.mbo.dev/?p=2055</guid>

					<description><![CDATA[The following script shows you steps to on an Apple Silicone Mac. I&#8217;ve also tested it with a script that allows to run with different numbers of parallel threads: Run on locally built python with GIL disabled: Run with same python version from brew with GIL enabled: Disabling GIL brings runtime down to 2.47 seconds [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>The following script shows you steps to</p>



<ul class="wp-block-list">
<li>download the python source code</li>



<li>compile it</li>



<li>integrate it into pyenv</li>



<li>and then use it</li>
</ul>



<p>on an Apple Silicone Mac.</p>



<pre class="wp-block-code"><code># prepare dependencies
brew install openssl readline zlib xz

# checkout python
git clone https://github.com/python/cpython.git
mkdir $HOME/.pythons
cd cpython
git checkout v3.13.3

# build
./configure \
  --prefix=$HOME/.pythons/python-3.13.3-nogil \
  --disable-gil \
  --enable-optimizations \
  CPPFLAGS="-I$(brew --prefix)/opt/openssl/include" \
  LDFLAGS="-L$(brew --prefix)/opt/openssl/lib"
make -j$(sysctl -n hw.ncpu)
make install

# integrate into pyenv
mkdir -p ~/.pyenv/versions/3.13.3-nogil
ln -s $HOME/.pythons/python-3.13.3-nogil ~/.pyenv/versions/3.13.3-nogil
# verify installation
pyenv versions | grep 3.13.3

pyenv shell 3.13.3-nogil
python3 --version
```
</code></pre>



<p>I&#8217;ve also tested it with a script that allows to run with different numbers of parallel threads:</p>



<pre class="wp-block-code"><code>import sys
import sysconfig
import math
import time
import threading
import argparse


def compute_factorial(n: int) -&gt; None:
    """Compute the factorial of a single number (no return, just for load)."""
    math.factorial(n)


def multi_threaded_compute(numbers: list&#91;int], thread_count: int) -&gt; None:
    """Distribute factorial computations across multiple threads."""
    threads = &#91;]
    # Split the workload into chunks for each thread
    chunks = &#91;numbers&#91;i::thread_count] for i in range(thread_count)]

    def worker(sublist: list&#91;int]) -&gt; None:
        for num in sublist:
            compute_factorial(num)

    # Start threads
    for sublist in chunks:
        thread = threading.Thread(target=worker, args=(sublist,))
        threads.append(thread)
        thread.start()

    # Wait for all threads to finish
    for thread in threads:
        thread.join()

    print("All factorials computed.")


def check_gil_status() -&gt; None:
    """Print whether the GIL is active or disabled."""
    gil_status = sysconfig.get_config_var("Py_GIL_DISABLED")
    if gil_status is None:
        print("GIL status: Unknown or not supported on this build")
    elif gil_status == 0:
        print("GIL is active")
    elif gil_status == 1:
        print("GIL is disabled")


def parse_args() -&gt; int:
    """Parse and return the number of threads from command-line args."""
    parser = argparse.ArgumentParser(description="Multithreaded factorial computation")
    parser.add_argument(
        "threads",
        type=int,
        help="Number of threads to use (must be a positive integer)",
    )
    args = parser.parse_args()

    if args.threads &lt;= 0:
        parser.error("Thread count must be a positive integer")

    return args.threads


def main():
    print(f"Python version: {sys.version}")
    check_gil_status()

    thread_count = parse_args()
    numbers = &#91;100_000, 200_000, 300_000, 400_000, 500_000]

    start_time = time.time()
    multi_threaded_compute(numbers, thread_count)
    elapsed_time = time.time() - start_time

    print(f"Time taken: {elapsed_time:.2f} seconds")


if __name__ == "__main__":
    main()</code></pre>



<p>Run on locally built python with GIL disabled:</p>



<pre class="wp-block-code"><code>% time python3 test.py $(sysctl -n hw.ncpu)

Python version: 3.13.3 experimental free-threading build (tags/v3.13.3:6280bb54784, Apr 11 2025, 13:01:07) &#91;Clang 17.0.0 (clang-1700.0.13.3)]
GIL is disabled
All factorials computed.
Time taken: 2.47 seconds
python3 test.py $(sysctl -n hw.ncpu)  6.09s user 0.04s system 235% cpu 2.608 total</code></pre>



<p>Run with same python version from brew with GIL enabled:</p>



<pre class="wp-block-code"><code>% time python3 test.py $(sysctl -n hw.ncpu)

Python version: 3.13.3 (main, Apr  8 2025, 13:54:08) &#91;Clang 16.0.0 (clang-1600.0.26.6)]
GIL is active
All factorials computed.
Time taken: 6.26 seconds
python3 test.py $(sysctl -n hw.ncpu)  6.26s user 0.04s system 99% cpu 6.360 total</code></pre>



<p>Disabling GIL brings runtime down to 2.47 seconds instead of 6.26. Disabling the GIL made the program about 60% faster <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f680.png" alt="🚀" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p>Check the cpu usage! It went up to 235% instead of 99%.</p>



<p>Here a small comparison to run the same in Kotlin with focus on multithreading:</p>



<pre class="wp-block-code"><code>import java.math.BigInteger
import kotlin.system.exitProcess
import kotlin.system.measureTimeMillis
import java.util.concurrent.Executors

fun computeFactorial(n: Int): BigInteger {
    var result = BigInteger.ONE
    for (i in 2..n) {
        result = result.multiply(BigInteger.valueOf(i.toLong()))
    }
    return result
}

fun multiThreadedCompute(numbers: List&lt;Int>, threadCount: Int) {
    val executor = Executors.newFixedThreadPool(threadCount)

    val tasks = numbers.map { n ->
        Runnable { computeFactorial(n) }
    }

    tasks.forEach { executor.submit(it) }

    executor.shutdown()
    while (!executor.isTerminated) {
        Thread.sleep(50)
    }

    println("All factorials computed.")
}

fun main(args: Array&lt;String>) {
    if (args.isEmpty()) {
        println("Usage: kotlin FactorialBenchmark.kt &lt;thread_count>")
        exitProcess(1)
    }

    val threadCount = args&#91;0].toIntOrNull()
    if (threadCount == null || threadCount &lt;= 0) {
        println("Please provide a valid positive integer for thread count.")
        exitProcess(1)
    }

    println("Kotlin version: ${KotlinVersion.CURRENT}")
    println("Running with $threadCount threads")

    val numbers = listOf(100_000, 200_000, 300_000, 400_000, 500_000)

    val elapsed = measureTimeMillis {
        multiThreadedCompute(numbers, threadCount)
    }

    println("Time taken: ${"%.2f".format(elapsed / 1000.0)} seconds")
}</code></pre>



<p>Result for the kotlin program running within JVM:</p>



<pre class="wp-block-code"><code>% time java -jar benchmark.jar $(sysctl -n hw.ncpu)
Kotlin version: 2.1.20
Running with 10 threads
All factorials computed.
Time taken: 92,55 seconds
java -jar benchmark.jar $(sysctl -n hw.ncpu)  199.58s user 1.49s system 217% cpu 1:32.65 total</code></pre>



<p>So python is killing Kotlin in this use case. With or without GIL.</p>



<p>For completeness I also tried it with Java 21. It was nearly the same timing. So in this case it didn&#8217;t matter if the code was Kotlin or Java.</p>



<p>Interestingly even the go variant didn&#8217;t outperform python here:</p>



<pre class="wp-block-code"><code>% time go run main.go $(sysctl -n hw.ncpu)
Go version: go1.24.2
GIL is not applicable in Go (true concurrency with goroutines and OS threads)
All factorials computed.
Time taken: 16.26 seconds
go run main.go $(sysctl -n hw.ncpu)  32.77s user 3.37s system 217% cpu 16.626 total</code></pre>



<pre class="wp-block-code"><code>package main

import (
	"fmt"
	"math/big"
	"os"
	"runtime"
	"strconv"
	"time"
)

func computeFactorial(n int) *big.Int {
	result := big.NewInt(1)
	tmp := big.NewInt(1)
	for i := 2; i &lt;= n; i++ {
		tmp.SetInt64(int64(i))
		result.Mul(result, tmp)
	}
	return result
}

func worker(jobs &lt;-chan int, done chan&lt;- bool) {
	for num := range jobs {
		computeFactorial(num)
	}
	done &lt;- true
}

func multiThreadedCompute(numbers &#91;]int, threadCount int) {
	jobs := make(chan int, len(numbers))
	done := make(chan bool)

	// Start fixed number of workers
	for i := 0; i &lt; threadCount; i++ {
		go worker(jobs, done)
	}

	// Send jobs
	for _, num := range numbers {
		jobs &lt;- num
	}
	close(jobs)

	// Wait for all workers to finish
	for i := 0; i &lt; threadCount; i++ {
		&lt;-done
	}

	fmt.Println("All factorials computed.")
}

func checkGILStatus() {
	fmt.Println("GIL is not applicable in Go (true concurrency with goroutines and OS threads)")
}

func main() {
	fmt.Println("Go version:", runtime.Version())
	checkGILStatus()

	if len(os.Args) != 2 {
		fmt.Println("Usage: go run main.go &lt;thread_count>")
		os.Exit(1)
	}

	threadCount, err := strconv.Atoi(os.Args&#91;1])
	if err != nil || threadCount &lt;= 0 {
		fmt.Println("Invalid thread count: must be a positive integer")
		os.Exit(1)
	}

	numbers := &#91;]int{100_000, 200_000, 300_000, 400_000, 500_000}

	start := time.Now()
	multiThreadedCompute(numbers, threadCount)
	elapsed := time.Since(start)

	fmt.Printf("Time taken: %.2f seconds\n", elapsed.Seconds())
}</code></pre>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>How to Determine Your GMMK Pro Revision (Rev1 vs. Rev2)</title>
		<link>https://blog.mbo.dev/archives/2048</link>
		
		<dc:creator><![CDATA[Manuel Bogner]]></dc:creator>
		<pubDate>Tue, 04 Feb 2025 00:01:16 +0000</pubDate>
				<category><![CDATA[General]]></category>
		<guid isPermaLink="false">https://blog.mbo.dev/?p=2048</guid>

					<description><![CDATA[If you own a Glorious GMMK Pro and want to find out whether you have Rev1 or Rev2, follow this detailed guide. The main differences between these revisions lie in their microcontroller (MCU) and firmware size, which we will analyze using DFU mode and dfu-util. Step 1: Enter DFU Mode on Your GMMK Pro To [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>If you own a <strong>Glorious GMMK Pro</strong> and want to find out whether you have <strong>Rev1 or Rev2</strong>, follow this detailed guide. The main differences between these revisions lie in their <strong>microcontroller (MCU)</strong> and <strong>firmware size</strong>, which we will analyze using <strong>DFU mode and dfu-util</strong>.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading"><strong>Step 1: Enter DFU Mode on Your GMMK Pro</strong></h2>



<p>To interact with the keyboard’s firmware, we need to put it into <strong>DFU (Device Firmware Upgrade) mode</strong>. To get out of this mode, simply power cycle the board. But make sure to NEVER power cycle a MCU while it is flashing. Otherwise you most likely will end up with a bricked device.</p>



<h3 class="wp-block-heading"><strong>How to Enter DFU Mode:</strong></h3>



<ol class="wp-block-list">
<li><strong>Unplug your keyboard</strong>.</li>



<li><strong>Hold down</strong> the <strong>Spacebar + B</strong> keys.</li>



<li><strong>Plug the keyboard back in</strong> while continuing to hold the keys.</li>



<li>Your computer should recognize the keyboard as a <strong>STM32 BOOTLOADER</strong> device.</li>
</ol>



<p>To confirm the device is in DFU mode, run:</p>



<pre class="wp-block-code"><code>lsusb | grep STM</code></pre>



<p>Expected output:</p>



<pre class="wp-block-code"><code>Bus 002 Device 005: ID 0483:df11 STMicroelectronics STM32 BOOTLOADER</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading"><strong>Step 2: Install dfu-util</strong></h2>



<p>We will use <code>dfu-util</code> to read and analyze the firmware stored on your GMMK Pro.</p>



<h3 class="wp-block-heading"><strong>Mac (Homebrew):</strong></h3>



<pre class="wp-block-code"><code>brew install dfu-util</code></pre>



<h3 class="wp-block-heading"><strong>Linux (Debian/Ubuntu):</strong></h3>



<pre class="wp-block-code"><code>sudo apt update &amp;&amp; sudo apt install dfu-util -y</code></pre>



<h3 class="wp-block-heading"><strong>Windows:</strong></h3>



<ul class="wp-block-list">
<li>Download <code>dfu-util</code> from the official <a href="http://dfu-util.sourceforge.net/">dfu-util page</a>.</li>



<li>Extract the binaries and use <code>dfu-util.exe</code> from the command line.</li>
</ul>



<p>After installation, verify it is installed by running:</p>



<pre class="wp-block-code"><code>dfu-util --version</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading"><strong>Step 3: Read the Firmware from Memory</strong></h2>



<p>Now that <code>dfu-util</code> is installed, let’s extract the firmware to check its size.</p>



<h3 class="wp-block-heading"><strong>Identify the DFU Device</strong></h3>



<pre class="wp-block-code"><code>sudo dfu-util -l</code></pre>



<p>Example output:</p>



<pre class="wp-block-code"><code>Found DFU: &#91;0483:df11] ver=2200, devnum=1, cfg=1, intf=0, path="0-1", alt=0, name="@Internal Flash /0x08000000/128*0002Kg", serial="205E36552033"</code></pre>



<h3 class="wp-block-heading"><strong>Dump the Firmware</strong></h3>



<p>Extract the firmware to a file for analysis:</p>



<pre class="wp-block-code"><code>sudo dfu-util -d 0483:df11 -a 0 -s 0x08000000:524288 -U gmmk_pro_firmware.bin</code></pre>



<ul class="wp-block-list">
<li><code>-d 0483:df11</code>: Specifies the device ID.</li>



<li><code>-a 0</code>: Selects the internal flash memory.</li>



<li><code>-s 0x08000000:524288</code>: Reads <strong>up to 512 KB</strong> from the microcontroller’s flash memory.</li>



<li><code>-U gmmk_pro_firmware.bin</code>: Saves the extracted firmware to a file.</li>
</ul>



<p>Now, check the firmware size:</p>



<pre class="wp-block-code"><code>ls -lh gmmk_pro_firmware.bin</code></pre>



<p>Expected output:</p>



<pre class="wp-block-code"><code>-rw-r--r--  1 user  staff   256K Jan 1 00:00 gmmk_pro_firmware.bin</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading"><strong>Step 4: Determine Revision Based on Firmware Size</strong></h2>



<p>The firmware size tells us which revision your keyboard is:</p>



<ul class="wp-block-list">
<li><strong>256 KB (Rev1)</strong> → Uses the <strong>STM32F303</strong> microcontroller.</li>



<li><strong>512 KB (Rev2)</strong> → Uses the <strong>STM32F411</strong> microcontroller.</li>
</ul>



<h3 class="wp-block-heading"><strong>Microcontroller Differences</strong></h3>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Revision</th><th>Microcontroller</th><th>Flash Size</th></tr></thead><tbody><tr><td><strong>Rev1</strong></td><td>STM32F303</td><td>256 KB</td></tr><tr><td><strong>Rev2</strong></td><td>STM32F411</td><td>512 KB</td></tr></tbody></table></figure>



<p>Since <strong>Rev1 uses a smaller flash partition (256 KB)</strong>, its firmware will never exceed that size. <strong>Rev2, on the other hand, requires larger firmware (closer to 512 KB).</strong></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading"><strong>Conclusion: Identifying Your GMMK Pro Revision</strong></h2>



<p>By following this guide, you can determine whether your <strong>GMMK Pro is Rev1 or Rev2</strong> by:</p>



<ol class="wp-block-list">
<li>Entering <strong>DFU mode</strong>.</li>



<li>Installing and using <strong>dfu-util</strong>.</li>



<li><strong>Extracting the firmware</strong> and checking its <strong>size</strong>.</li>
</ol>



<p>If your firmware size is <strong>256 KB</strong>, you have <strong>Rev1</strong>. If it&#8217;s closer to <strong>512 KB</strong> (over 256 KB), you have <strong>Rev2</strong>.</p>



<p>This method is <strong>non-intrusive</strong> and does not modify your keyboard’s firmware, ensuring a safe way to determine your revision.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Create Ramdisk on MacOS</title>
		<link>https://blog.mbo.dev/archives/2044</link>
		
		<dc:creator><![CDATA[Manuel Bogner]]></dc:creator>
		<pubDate>Sun, 05 Jan 2025 18:34:52 +0000</pubDate>
				<category><![CDATA[Mac]]></category>
		<guid isPermaLink="false">https://blog.mbo.dev/?p=2044</guid>

					<description><![CDATA[ramdisk.sh: This script creates a ramdisk with the first parameter taken as gigabytes. So runnning ./ramdisk.sh 16 will create a disk with 16 gigabytes and it will be named &#8220;ramdisk-16gb&#8221; and accessible via /Volumes/ramdisk-16gb. It will show up in finder as mounted volume. To get rid of it simply unmount it with the small icon [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p><em>ramdisk.sh</em>:</p>



<pre class="wp-block-code"><code>#!/bin/sh -eu
NUMSECTORS=$(($1*1024*1024*1024/512))
echo "creating ramdisk with $1 GB ($NUMSECTORS sectors)"
MYDEV=$(hdiutil attach -nomount ram://$NUMSECTORS)
echo "created $MYDEV"
diskutil eraseVolume HFS+ "ramdisk-${1}gb" $MYDEV
echo "formatted $MYDEV"</code></pre>



<p>This script creates a ramdisk with the first parameter taken as gigabytes. So runnning <code><strong>./ramdisk.sh 16</strong></code> will create a disk with 16 gigabytes and it will be named &#8220;ramdisk-16gb&#8221; and accessible via <strong><em>/Volumes/ramdisk-16gb</em></strong>. It will show up in finder as mounted volume. To get rid of it simply unmount it with the small icon right to the name.</p>



<p><strong>ATTENTION: Everything stored on this devices will be lost when the mac is turned off or the volume is removed.</strong></p>



<p></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Forcefully Stop HyperV VM</title>
		<link>https://blog.mbo.dev/archives/2042</link>
		
		<dc:creator><![CDATA[Manuel Bogner]]></dc:creator>
		<pubDate>Sat, 04 Jan 2025 13:49:39 +0000</pubDate>
				<category><![CDATA[Windows]]></category>
		<guid isPermaLink="false">https://blog.mbo.dev/?p=2042</guid>

					<description><![CDATA[Get the name as displayed in HyperV of the VM you want to stop and then run:]]></description>
										<content:encoded><![CDATA[
<p>Get the name as displayed in HyperV of the VM you want to stop and then run:</p>



<pre class="wp-block-code"><code>$VMGUID = (Get-VM "vm_name").ID
$VMWMProc = (Get-WmiObject Win32_Process | ? {$_.Name -match 'VMWP' -and $_.CommandLine -match $VMGUID})
Stop-Process ($VMWMProc.ProcessId) -Force</code></pre>



<p></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Sudo Timeout on MacOS</title>
		<link>https://blog.mbo.dev/archives/2040</link>
		
		<dc:creator><![CDATA[Manuel Bogner]]></dc:creator>
		<pubDate>Thu, 07 Nov 2024 19:38:48 +0000</pubDate>
				<category><![CDATA[Mac]]></category>
		<guid isPermaLink="false">https://blog.mbo.dev/?p=2040</guid>

					<description><![CDATA[I&#8217;m using homebrew and its casks a lot to install software on my MacBook. This way I&#8217;m using a custom script comparable to a simple apt update &#38;&#38; apt dist-upgrade on a Linux sysmtem. My update script looks like the following: Running this keeps most of my system up to date. I only have a [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>I&#8217;m using homebrew and its casks a lot to install software on my MacBook. This way I&#8217;m using a custom script comparable to a simple apt update &amp;&amp; apt dist-upgrade on a Linux sysmtem. My update script looks like the following:</p>



<pre class="wp-block-code"><code>#!/bin/zsh
source ./.zshrc

echo "starting local upgrades of homebrew, sdk and nvm\n"

echo "###########################"
echo "# homebrew"
echo "###########################"
# install see https://brew.sh
echo "update installed packages"
brew update
brew upgrade

echo "install packages"
brew install nvm
brew tap buo/cask-upgrade

echo "upgrade casks"
brew cu -ay

brew autoremove
brew cleanup

echo "\n\n\n"
echo "###########################"
echo "# sdk"
echo "###########################"

echo "install sdkman"
# https://sdkman.io/install
#curl -s "https://get.sdkman.io" | bash
sdk selfupdate

echo "update sdkman packages"
sdk update
sdk upgrade

echo "\n\n\n"
echo "###########################"
echo "# nvm"
echo "###########################"
echo "install LTS"
nvm install --lts

echo "\n\n\n"
echo "###########################"
echo "# npm"
echo "###########################"
npm update -g

echo "\n\n\n"
echo "###########################"
echo "done"</code></pre>



<p>Running this keeps most of my system up to date. I only have a few things installed either via AppStore or manually.</p>



<p>But where is the problem and what does this have to do with sudo you ask?</p>



<p>When you have a list of pending updates MacOS will ask you multiple time during a single run of the script for you password because sudo is using a very very short timeout for it. If you want to get rid of that problem run <code><strong>sudo viduso</strong></code> and add the following line after all those <code>Defaults</code> lines you will see in there:</p>



<pre class="wp-block-code"><code>Defaults	timestamp_timeout = -1</code></pre>



<p>This way sudo won&#8217;t forget your password in a single session. Of course that -1 could be a problem but for me it isn&#8217;t because I always close my terminals when I don&#8217;t need them anymore.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>HTML signature with Apple mail client</title>
		<link>https://blog.mbo.dev/archives/2038</link>
		
		<dc:creator><![CDATA[Manuel Bogner]]></dc:creator>
		<pubDate>Thu, 10 Oct 2024 15:28:56 +0000</pubDate>
				<category><![CDATA[Mac]]></category>
		<guid isPermaLink="false">https://blog.mbo.dev/?p=2038</guid>

					<description><![CDATA[Create a signature in the settings and add some placeholder text. Then close the mail client. The placeholder text will be replaced with html later. You will need the html code you want to use as signature. So have it ready to paste it later. Got to ~/Library/Mail/V10/MailData/Signatures and check which of the .mailsignature files [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>Create a signature in the settings and add some placeholder text. Then close the mail client. The placeholder text will be replaced with html later. </p>



<p>You will need the html code you want to use as signature. So have it ready to paste it later.</p>



<p>Got to <code><strong>~/Library/Mail/V10/MailData/Signatures</strong></code> and check which of the <em><strong>.mailsignature</strong></em> files is the one you want to change. For every signature saved in the mail client there is such a file. Just check the content. Open the right file and replace the html after the header. Everything above that empty line is the header. Here a sample that illustrates this:</p>



<pre class="wp-block-code"><code>Content-Transfer-Encoding: 7bit
Content-Type: text/html;
charset=us-ascii
Message-Id: &lt;12341234-1234-1234-1234-123412341234>
Mime-Version: 1.0 (Mac OS X Mail 16.0 (xxxx))

&lt;old content></code></pre>



<p>Leave everything as is and replace all text &#8220;&lt;old content>&#8221; with your new html. Save the file. The right click it and click &#8220;Get Info&#8221; and there you need to select &#8220;Locked&#8221;. Otherwise the mail client will overwrite the content and not use it.</p>



<p>With the file locked, open the mail client and you will have your html signature ready to use.</p>



<p>#Apple #MailClient #AppleMailClient #HTMLSignature</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>blog.coffeebeans.at moved to blog.mbo.dev</title>
		<link>https://blog.mbo.dev/archives/2034</link>
		
		<dc:creator><![CDATA[Manuel Bogner]]></dc:creator>
		<pubDate>Thu, 12 Sep 2024 18:19:30 +0000</pubDate>
				<category><![CDATA[General]]></category>
		<guid isPermaLink="false">https://blog.mbo.dev/?p=2034</guid>

					<description><![CDATA[Many years ago I registered coffeebeans.at and used it for various things. Right now I&#8217;m consolidating everything behind mbo.dev. You should be able to find everything from blog.coffeebeans.at behind blog.mbo.dev now. The old pages should automatically redirect to the new location. If you have any issues with the redirect or somethind doesn&#8217;t work anymore, please [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>Many years ago I registered coffeebeans.at and used it for various things. Right now I&#8217;m consolidating everything behind mbo.dev. You should be able to find everything from blog.coffeebeans.at behind blog.mbo.dev now. The old pages should automatically redirect to the new location. If you have any issues with the redirect or somethind doesn&#8217;t work anymore, please let me know via contact form.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Fix blackhole-2ch on MacOS &#8211; Could not kickstart service &#8220;com.apple.audio.coreaudiod&#8221;: 1: Operation not permitted</title>
		<link>https://blog.mbo.dev/archives/2021</link>
		
		<dc:creator><![CDATA[Manuel Bogner]]></dc:creator>
		<pubDate>Wed, 17 Apr 2024 18:41:42 +0000</pubDate>
				<category><![CDATA[Mac]]></category>
		<guid isPermaLink="false">https://blog.coffeebeans.at/?p=2021</guid>

					<description><![CDATA[Recently I received this error while upgrading or uninstalling blackhole-2ch via brew: Could not kickstart service &#8220;com.apple.audio.coreaudiod&#8221;: 1: Operation not permitted This was fixed by the following: Another option seems to be to fix the issue with kickstart as described here. But I didn&#8217;t try that.]]></description>
										<content:encoded><![CDATA[
<p>Recently I received this error while upgrading or uninstalling blackhole-2ch via brew:</p>



<p><strong><em>Could not kickstart service &#8220;com.apple.audio.coreaudiod&#8221;: 1: Operation not permitted</em></strong></p>



<p>This was fixed by the following:</p>



<pre class="wp-block-code"><code>sudo su
rm -rf /opt/homebrew/Caskroom/blackhole-2ch
exit
brew install blackhole-2ch</code></pre>



<p>Another option seems to be to fix the issue with kickstart as described <a href="https://github.com/ExistentialAudio/BlackHole/issues/779#issuecomment-2058581435">here</a>. But I didn&#8217;t try that.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>How to Replace Spring Dependency Management Gradle Plugin with a Version Catalog</title>
		<link>https://blog.mbo.dev/archives/2018</link>
		
		<dc:creator><![CDATA[Manuel Bogner]]></dc:creator>
		<pubDate>Tue, 05 Mar 2024 13:10:22 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://blog.coffeebeans.at/?p=2018</guid>

					<description><![CDATA[Are you looking for a cleaner alternative to managing dependencies in your Spring projects? While the Spring Dependency Management Gradle Plugin offers convenience, it may sometimes lead to issues such as transitive dependency conflicts. Enter the version catalog—a more streamlined solution. Let&#8217;s dive into how you can replace the Spring Dependency Management Gradle Plugin with [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>Are you looking for a cleaner alternative to managing dependencies in your Spring projects? While the Spring Dependency Management Gradle Plugin offers convenience, it may sometimes lead to issues such as transitive dependency conflicts. Enter the version catalog—a more streamlined solution. Let&#8217;s dive into how you can replace the Spring Dependency Management Gradle Plugin with a version catalog for smoother dependency management in your Spring projects.</p>



<h3 class="wp-block-heading">Why Replace the Spring Dependency Management Gradle Plugin?</h3>



<p>The Spring Dependency Management Gradle Plugin simplifies dependency management by providing a BOM (Bill of Materials) file that declares dependency versions. However, it can sometimes result in transitive dependency conflicts or inadvertently using outdated versions. By leveraging a version catalog, you gain greater control and flexibility over your project dependencies.</p>



<h3 class="wp-block-heading">Creating a Version Catalog</h3>



<p>Start by creating a <code>libs.versions.toml</code> file in the <code>gradle</code> folder of your project. This file will serve as your version catalog, containing sections for versions, libraries, and plugins. Here&#8217;s how it might look:</p>



<pre class="wp-block-code"><code>&#91;versions]
# libs
flyway = "10.8.1" # https://mvnrepository.com/artifact/org.flywaydb/flyway-core
# plugins
spring-boot = "3.2.3" # https://spring.io/projects/spring-boot
kotlin = "1.9.22" # https://kotlinlang.org/docs/releases.html#release-details
sonarqube = "4.3.1.3277" # https://plugins.gradle.org/plugin/org.sonarqube

&#91;libraries]
org-flywaydb-core = { module = "org.flywaydb:flyway-core", version.ref = "flyway" }
org-flywaydb-database-postgresql = { module = "org.flywaydb:flyway-database-postgresql", version.ref = "flyway" }

&#91;plugins]
org-springframework-boot = { id = "org.springframework.boot", version.ref = "spring-boot" }
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlin-spring = { id = "org.jetbrains.kotlin.plugin.spring", version.ref = "kotlin" }
kotlin-jpa = { id = "org.jetbrains.kotlin.plugin.jpa", version.ref = "kotlin" }
sonarqube = { id = "org.sonarqube", version.ref = "sonarqube" }
</code></pre>



<h3 class="wp-block-heading">Integrating Version Catalog in Gradle</h3>



<p>Now, let&#8217;s integrate the version catalog into your Gradle build using Kotlin DSL.</p>



<pre class="wp-block-code"><code>plugins {
    alias(libs.plugins.org.springframework.boot)
    alias(libs.plugins.kotlin.jvm)
    alias(libs.plugins.kotlin.spring)
    alias(libs.plugins.kotlin.jpa)

    alias(libs.plugins.sonarqube)
    jacoco
}

...

dependencies {
    implementation(platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES))
    implementation("org.springframework.boot:spring-boot-starter-actuator")
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    implementation("org.springframework.boot:spring-boot-starter-web")

    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("org.jetbrains.kotlin:kotlin-reflect")

    implementation(libs.org.sonarsource.sonar.ws)
    implementation(libs.swagger.annotations.v3)

    runtimeOnly("org.postgresql:postgresql")
    runtimeOnly(libs.org.flywaydb.core)
    runtimeOnly(libs.org.flywaydb.database.postgresql)

    testImplementation("org.springframework.boot:spring-boot-starter-test")
    testImplementation("org.springframework.boot:spring-boot-testcontainers")
    testImplementation("org.testcontainers:junit-jupiter")
    testImplementation("org.testcontainers:postgresql")
}</code></pre>



<p>So all versions are either coming from the Spring Boot BOM that is imported first or from the libs.versions.toml file which is taken in account automatically.</p>



<h3 class="wp-block-heading">Conclusion</h3>



<p>Replacing the Spring Dependency Management Gradle Plugin with a version catalog provides a cleaner and more flexible approach to dependency management in your Spring projects. By centralizing your dependency versions in a separate file, you gain more control and avoid potential conflicts or outdated versions. Give it a try in your next Spring project and experience smoother dependency management firsthand. Happy coding!</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Convert PSD files to PNG with ImageMagick in a simple bash script</title>
		<link>https://blog.mbo.dev/archives/2014</link>
		
		<dc:creator><![CDATA[Manuel Bogner]]></dc:creator>
		<pubDate>Mon, 12 Feb 2024 11:54:02 +0000</pubDate>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[Mac]]></category>
		<category><![CDATA[Media]]></category>
		<guid isPermaLink="false">https://blog.coffeebeans.at/?p=2014</guid>

					<description><![CDATA[Keeping original PSD files was quite good practice over the years but mostly other formats are needed down the road. Because opening Photoshop to export a file takes quite some time and isn&#8217;t efficient I wrote a small script to replace this task: This takes the file(s) to convert as argument(s). You can also just [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>Keeping original PSD files was quite good practice over the years but mostly other formats are needed down the road. Because opening Photoshop to export a file takes quite some time and isn&#8217;t efficient I wrote a small script to replace this task:</p>



<pre class="wp-block-code"><code>#!/usr/bin/env bash

if &#91;&#91; "" == "$@" ]]; then
    echo "usage: $0 &lt;file(s) to convert>"
    exit 1
fi

for i in "$@"; do
    directory=$(realpath $(dirname "$i"))
    file=$(basename "$i")
    name=${file%.*}
    extension=$(echo ${file##*.} | tr '&#91;:upper:]' '&#91;:lower:]')

    if &#91;&#91; "$extension" == "psd" ]]; then
        target="$directory/$name.png"
        if &#91;&#91; -e "$target" ]]; then
            echo "file already exists: '$target'"
        else
            echo "converting '$i'"
            convert "$i" -background none -flatten "$target" || exit 1
        fi
    fi
done</code></pre>



<p>This takes the file(s) to convert as argument(s). You can also just go with wildcards like <code>*.psd</code> to convert all psd files in a folder. Already converted files will be skipped.</p>



<p>You need ImageMagic installed on your machine to have access to the convert command.</p>



<p>Tested with ImageMagic 7.1.1-27 on MacOS Sonoma 14.3.1.</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
