Benedikt GrandeAbout me, my projects and thoughtsZola2024-03-13T17:00:00+00:00https://bgrande.de/atom.xmlWordPress, Nginx, Apache and webp images2024-03-13T10:36:14+00:002024-03-13T10:36:14+00:00https://bgrande.de/blog/wordpress-nginx-apache-and-webp-images/<p>So you want to use webp with WordPress but not install just another (paid) plugin?
With the help of some bash, ImageMagick and nginx or apache you're ready to go. </p>
<span id="continue-reading"></span><br>
<h2 id="why-not-installing-another-plugin">Why not installing another plugin?</h2>
<p>Feel free to buy one of the many image optimization and converting plugins out there!
Many of them are good and convenient. </p>
<p>I wanted to keep the plugin count down and not pay for something that's actually not that hard to achieve.</p>
<p>So I thought a bit about the problem, wrote a little bash script and did some research about the best ways to use the images in WordPress.
<br><br></p>
<h2 id="ok-now-show-me-the-script-please">Ok, now show me the script, please!</h2>
<p>Hold your horses! This is actually a very nice task which can be done quite easily if you have access to the webserver.
First, the requirements:</p>
<ol>
<li>You need access to your webserver via ssh (it's a bash script after all)</li>
<li>You need ImageMagick installed (we need <code>convert</code>)</li>
<li>You need to be able to create cronjobs (<code>crontab -e</code>)</li>
<li>For nginx, you need access to the webserver's config</li>
</ol>
<p>All set?
Let's continue then...</p>
<p>The script <code>convert-webp.bash</code>:</p>
<pre data-lang="bash" style="background-color:#fafafa;color:#61676c;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="font-style:italic;color:#abb0b6;">#!/bin/bash
</span><span>
</span><span style="color:#fa6e32;">if </span><span style="color:#f07171;">[ </span><span style="color:#ff8f40;">-z </span><span style="color:#86b300;">"$</span><span>1</span><span style="color:#86b300;">" </span><span style="color:#f07171;">]</span><span style="color:#ed9366;">; </span><span style="color:#fa6e32;">then
</span><span> </span><span style="color:#f07171;">echo </span><span style="color:#86b300;">"missing base target path parameter"
</span><span> </span><span style="color:#f07171;">exit</span><span> 1
</span><span style="color:#fa6e32;">fi
</span><span>
</span><span>BASE</span><span style="color:#ed9366;">=</span><span style="color:#86b300;">$</span><span>1
</span><span>
</span><span style="color:#f29718;">find </span><span style="color:#86b300;">"$</span><span>BASE</span><span style="color:#86b300;">"</span><span>/</span><span style="color:#ed9366;">*</span><span style="color:#ff8f40;"> -type</span><span> f </span><span style="color:#4cbf99;">\(</span><span style="color:#ff8f40;"> -iname </span><span style="color:#4cbf99;">\*</span><span>.jpg</span><span style="color:#ff8f40;"> -o -iname </span><span style="color:#4cbf99;">\*</span><span>.png</span><span style="color:#ff8f40;"> -o -iname </span><span style="color:#4cbf99;">\*</span><span>.jpeg </span><span style="color:#4cbf99;">\) </span><span style="color:#ed9366;">| </span><span style="color:#fa6e32;">while </span><span style="color:#f29718;">IFS</span><span>= read</span><span style="color:#ff8f40;"> -r</span><span> file</span><span style="color:#ed9366;">; </span><span style="color:#fa6e32;">do
</span><span> </span><span style="font-style:italic;color:#abb0b6;"># if the conversion target already exists, we continue
</span><span> </span><span style="color:#fa6e32;">if </span><span style="color:#f07171;">[ </span><span style="color:#ff8f40;">-f </span><span style="color:#86b300;">"$</span><span>file</span><span style="color:#86b300;">.webp" </span><span style="color:#f07171;">]</span><span style="color:#ed9366;">; </span><span style="color:#fa6e32;">then
</span><span> </span><span style="color:#fa6e32;">continue</span><span style="color:#ed9366;">;
</span><span> </span><span style="color:#fa6e32;">fi
</span><span>
</span><span> </span><span style="font-style:italic;color:#abb0b6;"># make sure we do not convert accidentally included webp files, again
</span><span> </span><span style="color:#fa6e32;">if </span><span style="color:#f07171;">[[ </span><span>$file </span><span style="color:#ed9366;">= *</span><span style="color:#86b300;">".webp" </span><span style="color:#f07171;">]]</span><span style="color:#ed9366;">; </span><span style="color:#fa6e32;">then
</span><span> </span><span style="color:#f07171;">echo </span><span style="color:#86b300;">"ignoring webp files"
</span><span> </span><span style="color:#fa6e32;">continue</span><span style="color:#ed9366;">;
</span><span> </span><span style="color:#fa6e32;">fi
</span><span>
</span><span> </span><span style="font-style:italic;color:#abb0b6;"># now doing the conversion, we could also tune the quality a bit here
</span><span> </span><span style="color:#f29718;">convert </span><span style="color:#86b300;">"$</span><span>file</span><span style="color:#86b300;">"</span><span style="color:#ff8f40;"> -quality</span><span> 50</span><span style="color:#ff8f40;"> -define</span><span> webp:lossless=false </span><span style="color:#86b300;">"$</span><span>file</span><span style="color:#86b300;">"</span><span>.webp
</span><span> </span><span style="color:#f07171;">echo</span><span> wrote </span><span style="color:#86b300;">"$</span><span>file</span><span style="color:#86b300;">.webp"
</span><span style="color:#fa6e32;">done
</span></code></pre>
<p>Now make the script executable: <code>chmod +x convert-webp.bash</code>.
The script can be called via <code>./convert-webp.bash path/to/your/wp-content/uploads</code>.</p>
<p>To create a cronjob: <code>crontab -e</code> and set:</p>
<pre data-lang="bash" style="background-color:#fafafa;color:#61676c;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#f29718;">*/30 </span><span style="color:#ed9366;">* * * *</span><span> bash /path/to/script/convert-webp.sh /path/to/your/wp-content/uploads
</span></code></pre>
<p>So the first part is done!
<br><br></p>
<h2 id="configure-nginx-or-apache">Configure nginx or apache</h2>
<p>Normally using webp if existing can be done with all webservers.
Nginx can do a lot of things especially for static files or for caching and that's why I chose it here. </p>
<h3 id="nginx-config-changes">Nginx config changes</h3>
<p>I did this first with nginx since I created my own webserver and added the following:
First, in the <code>http</code> level or the <code>root</code> level for a vhost definition, configure the webp suffix:</p>
<pre style="background-color:#fafafa;color:#61676c;"><code><span>map $http_accept $webp_suffix {
</span><span> default "";
</span><span> "~image/webp" ".webp";
</span><span>}
</span></code></pre>
<p>In the <code>server</code> level add a location (or change the existing) for images:</p>
<pre style="background-color:#fafafa;color:#61676c;"><code><span>location ~* \.(jpg|jpeg|gif|png|webp)$ {
</span><span> add_header Vary Accept;
</span><span> add_header Cache-Control "public, immutable";
</span><span> try_files $uri$webp_suffix $uri =404;
</span><span>
</span><span> expires 1y;
</span><span>}
</span></code></pre>
<p>This tells the webserver to always try using the webp_suffixed file if it exists.
Otherwise, it would use the original or give a 404 error if nothing found.</p>
<p>Also, using a long cache time is recommended for assets, so we're going with 1 year here.</p>
<h3 id="apache-htaccess-changes">Apache .htaccess changes</h3>
<p>Ok, I haven't tested this yet, but in theory it should look something like this:</p>
<pre data-lang="apacheconf" style="background-color:#fafafa;color:#61676c;" class="language-apacheconf "><code class="language-apacheconf" data-lang="apacheconf"><span>RewriteCond %{HTTP_ACCEPT} image/webp
</span><span>RewriteCond %{REQUEST_FILENAME} (.*)\.(png|gif|jpe?g)$
</span><span>RewriteCond %{REQUEST_FILENAME}\.webp -f
</span><span>RewriteRule ^ %{REQUEST_FILENAME}\.webp [L,T=image/webp]
</span><span>
</span><span><IfModule mod_headers.c>
</span><span> <FilesMatch "\.(png|gif|jpe?g)$">
</span><span> Header append Vary Accept
</span><span> </FilesMatch>
</span><span></IfModule>
</span></code></pre>
<p>This should do the same as the nginx config from above:</p>
<ol>
<li>Check if the webp file exists and use it as a replacement for any other image type. </li>
<li>Add the Vary Accept header</li>
</ol>
<p><br><br>
And now have fun!</p>
<p><br><br>
<strong>Image Attribution</strong><br>
Main image WordPress Plugins <a href="https://unsplash.com/de/@hostreviews?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Stephen Phillips - Hostreviews.co.uk</a> auf <a href="https://unsplash.com/de/fotos/flachbildschirm-sSPzmL7fpWc?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Unsplash</a></p>
Password Managers and why they are used2023-10-20T15:36:14+00:002023-10-20T15:36:14+00:00https://bgrande.de/blog/password-managers-and-why-they-are-used/<br>
Finally, there's my conclusion about a Tweet I did over a year ago...
<p>It was one of my <a href="https://twitter.com/benediktgrande/status/1510203993737334790">best tweets regarding engagement</a> was related to Password Managers and why people use them.</p>
<p>The question was:</p>
<blockquote>
<p>What password manager(s) do you use (or do you?)</p>
</blockquote>
<br>
<h2 id="takeaway-for-tweet-succession">Takeaway for tweet succession</h2>
<ul>
<li>It's good to start such a Tweet on weekends (start Saturday), around noon (12:34 this time) Central European Time.</li>
<li>On Monday, you get almost no answers anymore</li>
<li>Ask a simple question the target audience most likely has an opinion about</li>
<li>Engage with further questions to create engagement and discussion</li>
<li>Have a bit of luck
<br><br></li>
</ul>
<h2 id="the-votes">The Votes</h2>
<p>The following is not a conclusive list.
But, I distilled the 4 most mentioned Password Managers and the reasoning.</p>
<h3 id="bitwarden">Bitwarden</h3>
<ul>
<li><a href="https://bitwarden.com">https://bitwarden.com</a></li>
<li>49 votes</li>
<li>Bitwarden Twitter account caught up on the discussion</li>
</ul>
<h4 id="why">Why</h4>
<ul>
<li>Free (and generous free plan)</li>
<li>Open Source</li>
<li>Self-hosting possible</li>
<li>extensive 2FA with yubikey for paid version</li>
<li>even paid version relatively cheap</li>
<li>good family account</li>
<li>by now there's even a bitwarden <a href="https://github.com/dani-garcia/vaultwarden">Rust server</a>
<br><br></li>
</ul>
<h3 id="1password">1Password</h3>
<ul>
<li><a href="https://1password.com">https://1password.com</a></li>
<li>17 votes</li>
</ul>
<h4 id="why-1">Why</h4>
<ul>
<li>very convenient and easy to use</li>
<li>first password manager to be used and stayed with it</li>
<li>good family account</li>
<li>builtin 2FA</li>
<li>builtin ssh-agent</li>
<li>very good autofill
<br><br></li>
</ul>
<h3 id="keepass-x-xc-dx">Keepass(X|XC|DX)</h3>
<ul>
<li><a href="https://keepass.info">https://keepass.info</a></li>
<li>11 votes</li>
</ul>
<h4 id="why-2">Why</h4>
<ul>
<li>OpenSource</li>
<li>self-hosted</li>
<li>offline/no cloud
<br><br></li>
</ul>
<h3 id="lastpass">LastPass</h3>
<ul>
<li><a href="https://www.lastpass.com">https://www.lastpass.com</a></li>
<li>5 votes</li>
<li>many used this in the past but changed to Bitwarden because they skipped the free cross-platform usage</li>
</ul>
<h4 id="why-3">Why</h4>
<ul>
<li>good password generator</li>
<li>very good autofill</li>
<li>grouping</li>
<li>protected notes</li>
</ul>
<br>
<p>If you're further interested in this kind of topic, take a look at <a href="https://bgrande.de/projects/easy-password-handler/">my password handler without storing passwords at all</a>.</p>
<p>Also, as a developer one could think about using <a href="https://github.com/StackExchange/blackbox">blackbox</a> in combination with Git/GitHub.</p>
Rusty PHP - creating PHP extensions with Rust2023-10-06T20:36:14+00:002024-03-13T17:00:00+00:00https://bgrande.de/blog/rusty-php-creating-php-extensions-with-rust/<p>Recently, I took a look at the <a href="https://crates.io/crates/ext-php-rs">ext-php-rs</a> project.</p>
<p>With <em>ext-php-rs</em> you can very easily write extensions for PHP in Rust.</p>
<p>So, for testing, I developed a little extension implementing a function I recently used in native PHP.</p>
<span id="continue-reading"></span><br>
<h2 id="why-using-a-native-php-function-for-comparison-you-asked">Why using a native PHP function for comparison you asked?</h2>
<p><strong>For three reasons</strong>:</p>
<ol>
<li>Check the performance impact using an external Rust implementation compared to PHP native functions</li>
<li>Get an idea about how to develop an extension with <em>ext-php-rs</em></li>
<li>See how comfortable it is to do that
<br><br></li>
</ol>
<h2 id="setting-up-ext-php-rs">Setting up ext-php-rs</h2>
<p>If you have used Rust before, it's really very straightforward:</p>
<ol>
<li>Install PHP executable and the development packages</li>
<li>Create a new Rust project via <code>cargo new php-ext --lib</code></li>
<li>Now open the project in an IDE like RustRover or IntelliJ Community or whatever</li>
<li>Make sure the <em>Cargo.toml</em> looks is configured like this</li>
</ol>
<pre data-lang="toml" style="background-color:#fafafa;color:#61676c;" class="language-toml "><code class="language-toml" data-lang="toml"><span>[</span><span style="color:#399ee6;">lib</span><span>]
</span><span style="color:#399ee6;">crate-type </span><span>= [</span><span style="color:#86b300;">"cdylib"</span><span>]
</span><span>
</span><span>[</span><span style="color:#399ee6;">dependencies</span><span>]
</span><span style="color:#399ee6;">ext-php-rs </span><span>= </span><span style="color:#86b300;">"*"
</span><span>
</span><span>[</span><span style="color:#399ee6;">profile</span><span style="color:#61676ccc;">.</span><span style="color:#399ee6;">release</span><span>]
</span><span style="color:#399ee6;">strip </span><span>= </span><span style="color:#86b300;">"debuginfo"
</span></code></pre>
<p>Now we still need the cargo subcommand to run the php extensions:</p>
<pre data-lang="bash" style="background-color:#fafafa;color:#61676c;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#f29718;">cargo</span><span> install cargo-php
</span></code></pre>
<p>Via cargo php you can now install or delete extensions directly into your PHP environment.</p>
<p>The <em>lib.rs</em> can now look as simple as this:</p>
<pre data-lang="rust" style="background-color:#fafafa;color:#61676c;" class="language-rust "><code class="language-rust" data-lang="rust"><span>
</span><span style="color:#61676ccc;">#!</span><span>[</span><span style="color:#f29718;">cfg_attr</span><span>(windows</span><span style="color:#61676ccc;">, </span><span style="color:#f29718;">feature</span><span>(abi_vectorcall))]
</span><span>
</span><span style="color:#fa6e32;">use </span><span>ext_php_rs</span><span style="color:#ed9366;">::</span><span>prelude</span><span style="color:#ed9366;">::*</span><span style="color:#61676ccc;">;
</span><span>
</span><span style="color:#61676ccc;">#</span><span>[</span><span style="color:#f29718;">php_function</span><span>]
</span><span style="color:#fa6e32;">pub fn </span><span style="color:#f29718;">find_mapping</span><span>(</span><span style="color:#ff8f40;">needle</span><span style="color:#61676ccc;">:</span><span> String, </span><span style="color:#ff8f40;">haystack</span><span style="color:#61676ccc;">: </span><span style="font-style:italic;color:#55b4d4;">Vec</span><span><</span><span style="font-style:italic;color:#55b4d4;">String</span><span>>) </span><span style="color:#61676ccc;">-> </span><span style="font-style:italic;color:#55b4d4;">Option</span><span><</span><span style="font-style:italic;color:#55b4d4;">String</span><span>> {
</span><span> haystack</span><span style="color:#ed9366;">.</span><span style="color:#f07171;">iter</span><span>()</span><span style="color:#ed9366;">.</span><span style="color:#f07171;">find</span><span>(|</span><span style="color:#ed9366;">&</span><span style="color:#ff8f40;">elem</span><span>| elem </span><span style="color:#ed9366;">== &</span><span>needle)</span><span style="color:#ed9366;">.</span><span style="color:#f07171;">cloned</span><span>()
</span><span>}
</span><span>
</span><span style="font-style:italic;color:#abb0b6;">// Required to register the extension with PHP.
</span><span style="color:#61676ccc;">#</span><span>[</span><span style="color:#f29718;">php_module</span><span>]
</span><span style="color:#fa6e32;">pub fn </span><span style="color:#f29718;">module</span><span>(</span><span style="color:#ff8f40;">module</span><span style="color:#61676ccc;">:</span><span> ModuleBuilder) </span><span style="color:#61676ccc;">-></span><span> ModuleBuilder {
</span><span> module
</span><span>}
</span></code></pre>
<p><br><br>
A simple cargo build and cargo php install will enable you to use the function in any php file:</p>
<pre data-lang="php" style="background-color:#fafafa;color:#61676c;" class="language-php "><code class="language-php" data-lang="php"><span>find_mapping($testString, $randomStrings);
</span></code></pre>
<p><br><br></p>
<h2 id="conclusion">Conclusion</h2>
<p>Setting up the <em>ext-php-rs</em> project is very straightforward and knowing a bit of Rust you can create a new PHP extension in almost no time.</p>
<p>Also, most things can be done quite fine with better performance by native PHP functions so such an extension is especially suited for apps with compute intense processes or safety concerns that can be modularized into an extension.</p>
<p>Yes, I did some simple benchmarks here:</p>
<pre style="background-color:#fafafa;color:#61676c;"><code><span>time taken with ext-php-rs: '413.46788406372ms'
</span><span>time taken with PHP internal: '8.3808898925781ms'
</span><span>Time taken with native Rust: '4.85131ms'
</span></code></pre>
<p>As you can see, there's a large difference with the extension's performance. Let's find out why!
<br><br></p>
<h2 id="update-march-2024-and-the-reason-for-the-slower-rust-extension">Update March 2024 and the reason for the slower Rust extension</h2>
<p>I followed up this topic since I wanted to find the reason for the performance issues I encountered.
With a huge array there was a significant overhead of about 400ms above the PHP's native version which shouldn't normally happen with Rust. </p>
<p>So after posting <a href="https://github.com/davidcole1340/ext-php-rs/issues/303">the performance issue</a> to the <em>ext-php-rs</em> project's GitHub issue tracker and discussing with the nice people there.</p>
<p>The reason for the performance issues obviously is related to internal type conversions the extension has to do so using the Zend types instead improves the performance by a lot:</p>
<pre data-lang="Rust" style="background-color:#fafafa;color:#61676c;" class="language-Rust "><code class="language-Rust" data-lang="Rust"><span style="color:#61676ccc;">#</span><span>[</span><span style="color:#f29718;">php_function</span><span>]
</span><span style="color:#fa6e32;">pub fn </span><span style="color:#f29718;">find_mapping</span><span>(</span><span style="color:#ff8f40;">needle</span><span style="color:#61676ccc;">: </span><span style="color:#ed9366;">&</span><span>Zval, </span><span style="color:#ff8f40;">haystack</span><span style="color:#61676ccc;">: </span><span style="color:#ed9366;">&</span><span>ZendHashTable) </span><span style="color:#61676ccc;">-> </span><span style="font-style:italic;color:#55b4d4;">Option</span><span><</span><span style="font-style:italic;color:#55b4d4;">String</span><span>> {
</span><span> </span><span style="color:#fa6e32;">match</span><span> haystack</span><span style="color:#ed9366;">.</span><span style="color:#f07171;">iter</span><span>()</span><span style="color:#ed9366;">.</span><span style="color:#f07171;">position</span><span>(|</span><span style="color:#ff8f40;">elem</span><span>| elem</span><span style="color:#ed9366;">.</span><span style="color:#ff8f40;">1.</span><span style="color:#fa6e32;">str</span><span>() </span><span style="color:#ed9366;">==</span><span> needle</span><span style="color:#ed9366;">.</span><span style="color:#fa6e32;">str</span><span>()) {
</span><span> </span><span style="font-style:italic;color:#55b4d4;">None </span><span style="color:#ed9366;">=> </span><span style="font-style:italic;color:#55b4d4;">None</span><span style="color:#61676ccc;">,
</span><span> </span><span style="font-style:italic;color:#55b4d4;">Some</span><span>(_ind) </span><span style="color:#ed9366;">=></span><span> needle</span><span style="color:#ed9366;">.</span><span style="color:#f07171;">string</span><span>()
</span><span> }
</span><span>}
</span></code></pre>
<p>An even better solution came from <strong>ju1ius</strong>:</p>
<pre data-lang="Rust" style="background-color:#fafafa;color:#61676c;" class="language-Rust "><code class="language-Rust" data-lang="Rust"><span style="color:#61676ccc;">#</span><span>[</span><span style="color:#f29718;">php_function</span><span>]
</span><span style="color:#fa6e32;">pub fn </span><span style="color:#f29718;">find_mapping</span><span>(</span><span style="color:#ff8f40;">needle</span><span style="color:#61676ccc;">: </span><span style="color:#ed9366;">&</span><span>Zval, </span><span style="color:#ff8f40;">haystack</span><span style="color:#61676ccc;">: </span><span style="color:#ed9366;">&</span><span>ZendHashTable) </span><span style="color:#61676ccc;">-> </span><span style="font-style:italic;color:#55b4d4;">Option</span><span><ZVal> {
</span><span> </span><span style="color:#fa6e32;">let </span><span style="font-style:italic;color:#55b4d4;">Some</span><span>(needle) </span><span style="color:#ed9366;">=</span><span> needle</span><span style="color:#ed9366;">.</span><span style="color:#f07171;">zend_str</span><span>() </span><span style="color:#fa6e32;">else </span><span>{
</span><span> </span><span style="color:#fa6e32;">return </span><span style="font-style:italic;color:#55b4d4;">None</span><span style="color:#61676ccc;">; </span><span style="font-style:italic;color:#abb0b6;">// should rather be a type error, but oh well...
</span><span> }</span><span style="color:#61676ccc;">;
</span><span> haystack</span><span style="color:#ed9366;">.</span><span style="color:#f07171;">iter</span><span>()</span><span style="color:#ed9366;">.</span><span style="color:#f07171;">find_map</span><span>(|(_</span><span style="color:#61676ccc;">, </span><span style="color:#ff8f40;">value</span><span>)| value</span><span style="color:#ed9366;">.</span><span style="color:#f07171;">zend_str</span><span>()
</span><span> </span><span style="color:#ed9366;">.</span><span style="color:#f07171;">filter</span><span>(|</span><span style="color:#ff8f40;">zs</span><span>| zs </span><span style="color:#ed9366;">==</span><span> needle)
</span><span> </span><span style="color:#ed9366;">.</span><span style="color:#f07171;">map</span><span>(|_| value</span><span style="color:#ed9366;">.</span><span style="color:#f07171;">shallow_clone</span><span>())
</span><span> )
</span><span>}
</span></code></pre>
<p>So be careful if you're juggling with a lot of data in your extension.
Even with the above solution there's still some conversion going on impacting the performance. </p>
<p>Therefore, it's better making sure you have another way of reading the data in case there's a lot of it to prevent the type conversions.</p>
<br>
<p>And you still have to look at this adorable slightly rusty ElePHPant:
<img src="https://bgrande.de/blog/rusty-php-creating-php-extensions-with-rust/rusty-elephant-ideogram.jpg" alt="Another rusty 3D ElePHPant created with ideogram.ai" title="Another rusty 3D ElePHPant created with ideogram.ai" /></p>
<p><br><br>
<strong>Image Attribution</strong><br>
Both images created by <a href="https://ideogram.ai/g/_GuDUqY8Tnuk0pg5EqQRpQ/3">ideogram.ai</a></p>
How to restore a hard disk when it's not completely broken2023-10-05T10:36:14+00:002023-10-05T10:36:14+00:00https://bgrande.de/blog/how-to-restore-a-harddisk-when-its-not-completely-broken/<p>If you're still using HDDs (Hard disks) you might run into some issues with it over time.
It might even fail completely or just give you some corrupted data.
Here's how I restored a hard disk's data for a friend, recently. </p>
<span id="continue-reading"></span><br>
<h2 id="how-to-recognize-a-broken-hard-disk">How to recognize a broken hard disk</h2>
<p>A hard disk can fail in many ways.
I'm not an expert in hard disks and data recovery, but I had my fair share of failing disks and recovering data from it.
So, basic knowledge about that stuff exists.</p>
<h3 id="breaking-heads">Breaking heads</h3>
<p>Ok, this sounded funny... So I couldn't resist.</p>
<p>Either the head for reading/writing data breaks or its mechanics for moving it fail (which leads to <a href="https://youtu.be/F5Y7BniaRXg?feature=shared&t=25">funny sounds</a> from your hard disk).
In this case your best option is to either try to safe as many data as you can immediately or, if the data is too important <em>let a lab do the job</em> of repairing the head.
Which is, in many cases, really (and I mean REALLY) expensive. </p>
<p>Why? Because, every disk usage from now on will destroy the disk a bit more (i.e. the head can scratch on the platters).
Given, the head can move at all anymore, of course.</p>
<h3 id="bad-sectors">Bad sectors</h3>
<p>Or, some sectors on the disks fail. This is normal due to wear and tear the older the HDD gets.
Most of it can be fixed by some Software logic (either the HDD's <a href="https://en.wikipedia.org/wiki/Firmware">firmware</a> or the <a href="https://en.wikipedia.org/wiki/File_system">Filesystem</a>). </p>
<p>But! There might be a fast-growing number of bad sectors.
That's a hint for degrading disks and should lead to a fast replacement. </p>
<h3 id="corrupted-firmware-or-controller">Corrupted Firmware or Controller</h3>
<p>Then there's corrupted firmware: Sometimes the firmware controlling or even the controller chip(s) itself can fail.
In this case, very often, you'll lose access to the disk pretty fast.</p>
<h3 id="what-else">What else?</h3>
<p>Sometimes you get errors, i.e. corrupt data or the disk can't be found.
This might also be caused by a cable defect. So check the cables. A cable might just not be fully connected.
Or it broke. Try with another one.
<br><br></p>
<h2 id="how-to-prevent-hard-disk-failures">How to prevent hard disk failures</h2>
<p>There really is only one way: Do <a href="https://en.wikipedia.org/wiki/Backup">backups</a>, do them regularly and for the important data do at least an offsite and onsite backup.
For example one at home and one online. Two different locations (i.e. office and basement) are a good start as well.</p>
<p>Remember: Your hard disk will fail, eventually!
<br><br></p>
<h2 id="how-to-restore-a-broken-hard-disk-s-data">How to restore a broken hard disk's data</h2>
<p>So, as mentioned earlier, I had some fun restoring a friend's external hard disk not so long ago.
In contrast to how it sounded at first the disk still worked. But there were definitely issues.
Windows couldn't find the disk anymore, at least most of the time.
So I checked with another cable and tried using it without the device's internal USB Controller.
No luck. </p>
<p>So I tried with Linux. There I could get access to the disk. But only with huge lags to the 2 partitions. </p>
<p>After checking the S.M.A.R.T via <code>smartctl</code> data I concluded there were no obvious errors.</p>
<p>So, next I tried rewriting the partition table and repairing the <a href="https://en.wikipedia.org/wiki/Master_boot_record">MBR</a> via <a href="https://www.cgsecurity.org/wiki/TestDisk">TestDisk</a>.
Which worked. Now having access to the partitions without major lags. </p>
<p>Still no luck with Windows after that, though.</p>
<p>Having access to the filesystem via Linux now, I found some corrupt and missing files. </p>
<p>So I needed another tool: <a href="https://www.gnu.org/software/ddrescue/">ddrescue</a>.
With <code>ddrescue</code> you can restore deleted and sometimes corrupt (via filesystem) files.<br />
Using the command <code>ddrescue /dev/sdx1 /path/to/imagefile.img</code> I could write all the potential data to an image file.
Be careful here, depending on the disk or partition size the resulting file is going to be (very) large.
For that reason you might need a big disk for storing the image. </p>
<p>Well, that worked. Now I got a new HDD and started copying the rescued data back to the new disk (using <a href="https://en.wikipedia.org/wiki/Rsync">rsync</a>).
That's where the <em>'I'm not an expert'</em> part came in.
Somehow I (or <code>ddrescue</code> and I didn't know how to prevent this) mixed up the file encoding.
All rescued files were formatted as <code>utf-32-le</code> which mixed up the filenames with special chars (my friend is German, so yeah), therefore I couldn't copy them all.</p>
<p>Luckily, there's a way to fix that as well.
Mounting the image into a loop device and changing the encoding via <code>convmv -f utf-32-le -t utf-8</code> fixed the names, and so I started copying, again.</p>
<p>That took a while, of course.</p>
<p>After finishing the backup and testing the new HDD in windows everything went fine.
Well, I had to repair the MBR via NTFS tools on Windows, again, at first.
But this time it worked and my friend can now use her data, again. </p>
<p><br><br>
<strong>Image Attribution</strong><br>
Article Picture from <a href="https://unsplash.com/de/@redaquamedia?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Denny Müller</a> auf <a href="https://unsplash.com/de/fotos/4u6TUbreFc0?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Unsplash</a></p>
The new fight against remote work2023-05-10T10:36:14+00:002023-11-17T16:49:00+00:00https://bgrande.de/blog/the-new-fight-against-remote/<p>After saving a lot of companies during COVID-19 many companies picked working from home as their new bargaining topic and declare remote work or workers as the reason for bad economics. </p>
<span id="continue-reading"></span><br>
<h2 id="why-is-remote-work-bad-now">Why is remote work bad now?</h2>
<p>You probably already heard about it, you might even be affected by it: <a href="https://www.businessinsider.com/fight-return-to-office-mandates-remote-work-amazon-apple-2023-3">Companies are eager to get their employees back to the office</a>. </p>
<p>While most of the time the reasoning given by the company's leaders is something like <em>People are more creative in the office</em> or <em>People are less productive when working from home</em> in my opinion there's more to it than what has been communicated.</p>
<p>First of all, to my knowledge, there's no proof for remote work making companies less productive (other studies <a href="https://www.forbes.com/sites/bryanrobinson/2022/02/04/3-new-studies-end-debate-over-effectiveness-of-hybrid-and-remote-work/">hint to the opposite</a>).
Also, there's evidence that those companies complaining about working from home are handling remote work just wrong, or the remote work setup revealed underlying systemic issues.
And yes, in those companies with severe structural and people issues <a href="https://fortune.com/2023/05/04/ibm-ceo-says-remote-workers-careers-suffer/">your career will definitely suffer</a>, especially when working remote.</p>
<p><img src="https://i.imgflip.com/7lcae6.jpg" alt="Meme about remote and office work where companies crazily take the exit for the office" /></p>
<h3 id="there-s-more-to-it">There's more to it</h3>
<p>For starters, there's this big building called an office where cattle you own can be cooped closely together.
Pardon, did I say cattle? I meant talents, of course! Obviously, I'm a little bit exaggerating, here.
But it can sure feel that way. </p>
<p>So, back to the topic: What to do with those buildings?
You financed them, are renting them, they have pretty furnishings, it's part of the operating costs.
So yeah, you want to use them. </p>
<p>And then, what about the people you own? Err, you employ!
It's nice to have them around and ensure those little peasants do what they're paid for, right?<br />
Here you go, there's your second reason for getting them back to the office!
Trust definitely is a difficult thing to handle.</p>
<p>Still not enough? Yeah, you know, turns out, women might lose social connection and being left alone at home due to traditional role models when not going to the office!
Really? While in rare occasions this might be true you should really let the women in question decide about that!
<img src="https://i.imgflip.com/7lcazn.jpg" alt="But! women! - angry boomer meme" /></p>
<p>But wait, that's not all! There's that <a href="https://fortune.com/2023/05/05/remote-work-productivity-5-straight-quarters-decline-gregory-daco/">declining rate of productivity</a>, for the first time after WWII.
Surely, the reason for that must be the working from home madness every lazy inferior wants to do since COVID-19 hit the globe!</p>
<p>Or does it? The same article from above also mentions two other possible reasons: </p>
<ol>
<li>Ill-implemented Remote or Hybrid setup, as in the companies are not providing the necessary tools and training</li>
<li>Record high employee churn
<br><br></li>
</ol>
<h3 id="actually-it-s-about-power">Actually it's about power</h3>
<p>Who could have guessed that? Firing people and (not) hiring new ones drains on productivity?
Or not providing adequate payment or working environment people want to work with, so they quit?
Combined with a higher amount of retirements this leads to having to train new people and current employees to handle a possibly much higher workload.</p>
<p>Turns out, people have to be trained on new jobs, they need some time to adjust and learn how the company and their jobs work.
Which, I give them that, might take a bit longer via working from home.
And, believe it or not, those employees might want a fair payment amongst a fair treatment!</p>
<p>While some of them <a href="https://www.businesstoday.in/technology/news/story/chatgpt-maker-sam-altman-says-era-of-remote-work-is-over-380090-2023-05-04">go into wild explanations</a> of missing out on creativity,
even creating things as a whole, at the same time, they confirm offering remote work and having very talented and productive employees working from home.</p>
<p>Either mister GPT got a bit confused here or well, he's just singing from the same hymn sheet as those other CEOs from Amazon, Google, Salesforce and whatnot without any real reason, much less sound arguments.
But one: Having more power over employees. </p>
<p>And that's what it really is all about, in my opinion: Getting back control over employees, adding another bargaining chip in the battle for (cheap) talents.
In a time of potential recession and having to pay very high salaries for, especially, Software Developers, those companies feel for getting back an edge over their employees.
Meaning: Cut back salaries and perceived perks, while threatening with losing the job. Which is greatly emphasized by the recent mass lay-offs.</p>
<p>And, of course, you can make people quit in times you would want to lay off a larger amount of people without signaling too much bad to the shareholders.
If you don't take it from me, <a href="https://youtu.be/jrsRvozsUQ8" title="Link to YouTube video Why Companies NEED People Back In The Office">take it from a larger YouTube channel</a></p>
<p>Only, remote work <a href="https://fortune.com/2023/05/04/remote-work-bargaining-chip-problem-careers-job-van-der-voort/">might just not be another perk or bargaining chip</a> and those actions could backfire sooner or later.
Contradicting voices from the employee's side also put it bluntly: <a href="https://www.smh.com.au/national/let-me-work-from-home-or-i-won-t-work-for-you-it-s-that-simple-20230326-p5cvb0.html">Let me work from home, or I won’t work for you, it’s that simple</a>.
And I tend to agree with that sentiment.
<img src="https://i.imgflip.com/7lc8um.jpg" alt="Meme about remote and office work reverse order" /></p>
<p>Many employees and employers have found working from home to be a great fit, the former especially for work-life-balance, the latter for gaining access to otherwise not accessible talents.
Therefore, I don't think, working from home is going to disappear anytime soon. But it might get a bit harder for a while.
<br><br></p>
<h2 id="reasons-to-work-remote">Reasons to work remote</h2>
<p>It starts with your stance on remote work.
Either you're the type of person who likes to work in a more quiet and sole environment while structuring the daily work well on your own. </p>
<p>Or you're more into working close to other people and/or not good in self-organizing your work. </p>
<p>In my opinion none of these characteristics are better than the other. It's just a different type of personality. </p>
<p>If you're the former, there should be no reason (apart from the obvious like the actual job requires to be on-site) to not work remotely.
Of course, there are a few more things to <a href="/blog/12-things-you-should-know-when-working-from-home/">consider and know about working remote</a> like having a quiet place and ergonomic equipment.</p>
<p>Well, or, if you're a <a href="https://archive.is/2023.07.18-201810/https://fortune.com/2023/07/18/new-research-suggests-bankers-are-5-times-less-likely-engage-financial-misconduct-working-home-careers-finance-gleb-tsipursky/">bunch of gangsters, pardon me, banksters</a> it might be a way
preventing financial misconduct. So, now, if the bank people want to return to the office, you got an idea why!
<br><br></p>
<h2 id="reasons-not-to-work-remote">Reasons not to work remote</h2>
<p>This is not an exhaustive list of reasons not to work remote, because there are some!</p>
<p>The obvious reason: The product you're creating or working on requires you to be on-site. Or you're a salesclerk.
Or as mentioned before: You're not the type of person who likes it, maybe even feeling lonely, or you're not able to sufficiently self-organize. Totally fine, go on, work in the office.
<br><br></p>
<h2 id="why-companies-should-offer-remote-work">Why companies should offer remote work</h2>
<p>When offering working from home, companies can get access to talents otherwise impossible to reach. </p>
<p>Also, remote work can <a href="https://www.forbes.com/sites/bryanrobinson/2022/05/05/remote-work-increases-employee-happiness-by-20-new-study-finds/">increase employees' happiness</a> since it often means a better work-life-balance.</p>
<p>Doing it right, meaning having a well-thought-out remote-first approach, can even give you more productivity than working from the office only.
Of course, there's never either or! With a (working with remote first approach) hybrid setup you can have both, people working on-site and from home (or whereever).
And you can let your employees decide what fits best for them at a given time.</p>
<p>Workplace flexibility, it seems, at least correlates <a href="https://www.forbes.com/sites/jenamcgregor/2023/11/14/companies-with-flexible-remote-work-policies-outperform-on-revenue-growth-report/" title="Link to external article about flexible remote work policies correlating with great revenue performance growth">with great revenue performance</a>...
Can't be that bad, can it?</p>
<p>Last but not least, I'm not saying, that remote work is the silver bullet. As mentioned before, it's not for everyone and every configuration.
That being said, especially in tech, it can be a huge win for companies and employees alike.</p>
<p><br><br>
<strong>Image Attribution</strong><br>
Main remote work setup image from <a href="https://unsplash.com/@domenicoloia?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Domenico Loia</a> auf <a href="https://unsplash.com/de/fotos/hGV2TfOh0ns?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a><br>
Flame overlay image from <a href="https://unsplash.com/@joshuas?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Joshua Sukoff</a> auf <a href="https://unsplash.com/de/fotos/AERa18umg2w?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a><br>
Memes have been created with <a href="https://imgflip.com/">imgflip</a></p>
Rust and OpenSSL and static linking2023-05-04T16:56:12+00:002023-05-04T16:56:12+00:00https://bgrande.de/blog/rust-and-openssl-and-static-linking/<h2 id="today-i-learned">Today I learned</h2>
<p>While the default linking is dynamic for Rust and using OpenSSL you can change the default and use static linking as well.
For my use case I had a different setup on the build machine and the target deployment (yes, no docker here).
So I needed to statically link OpenSSL instead. </p>
<p>Turns out <a href="https://users.rust-lang.org/t/how-to-link-openssl-statically/14912/2">you can very easily change</a> that in Rust via:</p>
<pre data-lang="bash" style="background-color:#fafafa;color:#61676c;" class="language-bash "><code class="language-bash" data-lang="bash"><span>OPENSSL_STATIC</span><span style="color:#ed9366;">=</span><span style="color:#86b300;">yes </span><span>OPENSSL_LIB_DIR</span><span style="color:#ed9366;">=</span><span style="color:#86b300;">/usr/lib/x86_64-linux-gnu </span><span>OPENSSL_INCLUDE_DIR</span><span style="color:#ed9366;">=</span><span style="color:#86b300;">/usr/include/ </span><span style="color:#f29718;">cargo</span><span> build
</span></code></pre>
<p>The first <code>OPENSSL_STATIC</code> param tells cargo to use static binding for OpenSSL.
Whilst the second param <code>OPENSSL_LIB_DIR</code> is used to specify OpenSSL's library folder.
Last but not least we have <code>OPENSSL_INCLUDE_DIR</code> for telling cargo where to find the OpenSSL header files. </p>
<p>And that's it. </p>
<p>Of course, in most cases you probably want dynamic linking (and get a smaller executable) but in some cases this helps a lot. </p>
Deserialize JSON field with multiple possible types in Rust2023-01-22T16:29:16+00:002023-01-22T16:29:16+00:00https://bgrande.de/blog/custom-deserialization-of-multiple-type-field-from-json-in-rust/<p>Ever wondered about serde's custom deserialization because of multiple possible types?
Me too!
After some researching and testing I found a way to easily convert all possible data types into one single type so your application can handle it. </p>
<span id="continue-reading"></span><br>
<h2 id="rust-and-strict-types-the-problem">Rust and strict types - the problem</h2>
<p>Rust is a very type safe language, so it's kind of strict with allowing only one specific type per field or variable.
This is great but might complicate things a bit if you have to compute, for example, a JSON file where a field (or many) can have multiple types.
Like <code>boolean</code>, <code>array</code>, <code>integer</code> and <code>string</code> or maybe even <code>null</code>.<br>
So what can we do about this in Rust? Well, using <a href="https://serde.rs/">serde</a> to deserialize the JSON file already helps a lot.<br>
And the fact that you can customize the deserialization (as well as serialization of course) brings us close to a solution.</p>
<p><img src="https://bgrande.de/blog/custom-deserialization-of-multiple-type-field-from-json-in-rust/meme-one-does-not-only-use-multiple-types-per-variable.jpg" alt="arewefuckedyet.com's doomsday clock" title="Meme: One does not simply use multiple types per variable" />
<br><br></p>
<h2 id="customized-deserialization">Customized deserialization</h2>
<p>Now, you can do a lot with serde, most things work pretty much out of the box.
Therefore, in many cases, deserializing a JSON payload is as easy as assigning it a struct.<br>
Even when we're dealing with optional values (i.e., a field not always set in the payload) it's just a matter of setting
<code>#[serde(default)]</code> for the field in question in the associated struct.</p>
<h3 id="how-to-handle-multiple-types">How to handle multiple types</h3>
<p>Like explained above, it gets a bit more complicated when you want to handle a field which can have multiple different types.
Basically, what we need now, is a custom deserializer (and serializer for the other way around).</p>
<p>In theory, there are at least three ways to do this:<br>
You can either create multiple fields in your struct for each type and try to assign the values depending on the type.
I haven't tried this, yet. Therefore, I'm not entirely sure if it works.</p>
<p>Another way is using an enum like the following code: </p>
<pre data-lang="Rust" style="background-color:#fafafa;color:#61676c;" class="language-Rust "><code class="language-Rust" data-lang="Rust"><span style="color:#61676ccc;">#</span><span>[</span><span style="color:#f29718;">derive</span><span>(Debug</span><span style="color:#61676ccc;">,</span><span> Serialize</span><span style="color:#61676ccc;">,</span><span> Deserialize)]
</span><span style="color:#61676ccc;">#</span><span>[</span><span style="color:#f29718;">serde</span><span>(untagged)]
</span><span style="color:#fa6e32;">enum </span><span style="color:#399ee6;">MultipleTypes </span><span>{
</span><span> Str(</span><span style="font-style:italic;color:#55b4d4;">String</span><span>)</span><span style="color:#61676ccc;">,
</span><span> </span><span style="font-style:italic;color:#55b4d4;">Vec</span><span>(</span><span style="font-style:italic;color:#55b4d4;">Vec</span><span><</span><span style="font-style:italic;color:#55b4d4;">String</span><span>>)
</span><span> Bool(</span><span style="color:#fa6e32;">bool</span><span>)</span><span style="color:#61676ccc;">,
</span><span> </span><span style="color:#ff8f40;">U64</span><span>(</span><span style="color:#fa6e32;">u64</span><span>)</span><span style="color:#61676ccc;">,
</span><span>}
</span></code></pre>
<br>
<p>This case might even work without a custom serializer by using the struct value like:</p>
<pre data-lang="Rust" style="background-color:#fafafa;color:#61676c;" class="language-Rust "><code class="language-Rust" data-lang="Rust"><span style="color:#61676ccc;">#</span><span>[</span><span style="color:#f29718;">derive</span><span>(Debug</span><span style="color:#61676ccc;">,</span><span> Serialize</span><span style="color:#61676ccc;">,</span><span> Deserialize)]
</span><span style="color:#fa6e32;">struct </span><span style="color:#399ee6;">MultiStruct </span><span>{
</span><span> key_1</span><span style="color:#61676ccc;">: </span><span style="color:#fa6e32;">i32</span><span>,
</span><span> key_2</span><span style="color:#61676ccc;">: </span><span style="color:#fa6e32;">bool</span><span>,
</span><span> key_3</span><span style="color:#61676ccc;">:</span><span> MultipleTypes,
</span><span>}
</span></code></pre>
<p>With <code>key_3</code> having the <code>MultipleTypes</code> enum and therefore able to hold multiple types thanks to the previously defined enum.<br>
You could even handle <code>null</code> values by using <code>Option</code> like <code>Option<MultipleTypes></code>.</p>
<p>For my use-case, the downside of this approach was that I would have to resolve the value on each usage.</p>
<p><strong>So, there's another way</strong>:<br>
You can create a custom deserializer for serde which can be used for the field related to multiple types.
All the deserializer function does is handling and converting the different types into a single result type.</p>
<pre data-lang="Rust" style="background-color:#fafafa;color:#61676c;" class="language-Rust "><code class="language-Rust" data-lang="Rust"><span style="color:#fa6e32;">fn </span><span style="color:#f29718;">parse_value</span><span><</span><span style="color:#fa6e32;">'de</span><span>, D>(</span><span style="color:#ff8f40;">deserializer</span><span style="color:#61676ccc;">:</span><span> D) </span><span style="color:#61676ccc;">-> </span><span style="font-style:italic;color:#55b4d4;">Result</span><span><</span><span style="font-style:italic;color:#55b4d4;">Option</span><span><</span><span style="font-style:italic;color:#55b4d4;">Vec</span><span><</span><span style="font-style:italic;color:#55b4d4;">String</span><span>>>, </span><span style="color:#fa6e32;">D</span><span style="color:#ed9366;">::</span><span>Error>
</span><span> </span><span style="color:#fa6e32;">where
</span><span> D</span><span style="color:#61676ccc;">: </span><span>Deserializer<</span><span style="color:#fa6e32;">'de</span><span>>,
</span><span>{
</span><span> </span><span style="color:#61676ccc;">#</span><span>[</span><span style="color:#f29718;">derive</span><span>(Deserialize)]
</span><span> </span><span style="color:#61676ccc;">#</span><span>[</span><span style="color:#f29718;">serde</span><span>(untagged)]
</span><span> </span><span style="color:#fa6e32;">enum </span><span style="color:#399ee6;">AnyType</span><span><'a> {
</span><span> Str(</span><span style="color:#ed9366;">&</span><span style="color:#fa6e32;">'a str</span><span>)</span><span style="color:#61676ccc;">,
</span><span> </span><span style="color:#ff8f40;">U64</span><span>(</span><span style="color:#fa6e32;">u64</span><span>)</span><span style="color:#61676ccc;">,
</span><span> </span><span style="font-style:italic;color:#55b4d4;">Vec</span><span>(</span><span style="font-style:italic;color:#55b4d4;">Vec</span><span><</span><span style="font-style:italic;color:#55b4d4;">String</span><span>>)</span><span style="color:#61676ccc;">,
</span><span> Bool(</span><span style="color:#fa6e32;">bool</span><span>)</span><span style="color:#61676ccc;">,
</span><span> </span><span style="font-style:italic;color:#55b4d4;">None</span><span style="color:#61676ccc;">,
</span><span> }
</span><span>
</span><span> </span><span style="font-style:italic;color:#55b4d4;">Ok</span><span>(</span><span style="color:#fa6e32;">match </span><span>AnyType</span><span style="color:#ed9366;">::</span><span>deserialize(deserializer)</span><span style="color:#ed9366;">? </span><span>{
</span><span> AnyType</span><span style="color:#ed9366;">::</span><span>Str(v) </span><span style="color:#ed9366;">=> </span><span style="font-style:italic;color:#55b4d4;">Some</span><span>(</span><span style="color:#f07171;">vec!</span><span>[v</span><span style="color:#ed9366;">.</span><span style="color:#f07171;">to_string</span><span>()])</span><span style="color:#61676ccc;">,
</span><span> AnyType</span><span style="color:#ed9366;">::</span><span style="color:#ff8f40;">U64</span><span>(v) </span><span style="color:#ed9366;">=> </span><span style="font-style:italic;color:#55b4d4;">Some</span><span>(</span><span style="color:#f07171;">vec!</span><span>[v</span><span style="color:#ed9366;">.</span><span style="color:#f07171;">to_string</span><span>()])</span><span style="color:#61676ccc;">,
</span><span> AnyType</span><span style="color:#ed9366;">::</span><span>Vec(v) </span><span style="color:#ed9366;">=> </span><span style="font-style:italic;color:#55b4d4;">Some</span><span>(v)</span><span style="color:#61676ccc;">,
</span><span> AnyType</span><span style="color:#ed9366;">::</span><span>Bool(v) </span><span style="color:#ed9366;">=> </span><span style="font-style:italic;color:#55b4d4;">Some</span><span>(</span><span style="color:#f07171;">vec!</span><span>[v</span><span style="color:#ed9366;">.</span><span style="color:#f07171;">to_string</span><span>()])</span><span style="color:#61676ccc;">,
</span><span> AnyType</span><span style="color:#ed9366;">::</span><span>None </span><span style="color:#ed9366;">=> </span><span style="font-style:italic;color:#55b4d4;">None</span><span style="color:#61676ccc;">,
</span><span> })
</span><span>}
</span></code></pre>
<p>This is similar to the first approach with the main difference that we're converting all the values into a vector of strings.
Since I didn't need the real types here, it was the best option at the time.<br>
The code for using the custom deserialization function in a struct looks like that:</p>
<pre data-lang="Rust" style="background-color:#fafafa;color:#61676c;" class="language-Rust "><code class="language-Rust" data-lang="Rust"><span style="color:#61676ccc;">#</span><span>[</span><span style="color:#f29718;">derive</span><span>(Debug</span><span style="color:#61676ccc;">,</span><span> Serialize</span><span style="color:#61676ccc;">,</span><span> Deserialize)]
</span><span style="color:#fa6e32;">struct </span><span style="color:#399ee6;">MultiStruct </span><span>{
</span><span> key_1</span><span style="color:#61676ccc;">: </span><span style="color:#fa6e32;">i32</span><span>,
</span><span> key_2</span><span style="color:#61676ccc;">: </span><span style="color:#fa6e32;">bool</span><span>,
</span><span> </span><span style="color:#61676ccc;">#</span><span>[</span><span style="color:#f29718;">serde</span><span>(deserialize_with</span><span style="color:#ed9366;">=</span><span style="color:#86b300;">"parse_value"</span><span>)]
</span><span> key_3</span><span style="color:#61676ccc;">: </span><span style="font-style:italic;color:#55b4d4;">Option</span><span><</span><span style="font-style:italic;color:#55b4d4;">Vec</span><span><</span><span style="font-style:italic;color:#55b4d4;">String</span><span>>>,
</span><span>}
</span></code></pre>
<p><br><br>
Now, accessing it in your code needs resolving the Option first, i.e. like:</p>
<pre data-lang="Rust" style="background-color:#fafafa;color:#61676c;" class="language-Rust "><code class="language-Rust" data-lang="Rust"><span style="color:#fa6e32;">let</span><span> value </span><span style="color:#ed9366;">=</span><span> field</span><span style="color:#ed9366;">.</span><span>value</span><span style="color:#ed9366;">.</span><span style="color:#f07171;">to_owned</span><span>()</span><span style="color:#61676ccc;">;
</span><span style="color:#fa6e32;">let</span><span> current_value </span><span style="color:#ed9366;">= </span><span style="color:#fa6e32;">match</span><span> value {
</span><span> </span><span style="font-style:italic;color:#55b4d4;">Some</span><span>(value) </span><span style="color:#ed9366;">=></span><span> value</span><span style="color:#61676ccc;">,
</span><span> </span><span style="font-style:italic;color:#55b4d4;">None </span><span style="color:#ed9366;">=> </span><span style="color:#fa6e32;">continue</span><span style="color:#61676ccc;">,
</span><span>}</span><span style="color:#61676ccc;">;
</span><span>
</span><span style="font-style:italic;color:#abb0b6;">// now do sth. with the first element
</span><span style="color:#fa6e32;">let</span><span> val </span><span style="color:#ed9366;">=</span><span> current_value[</span><span style="color:#ff8f40;">0</span><span>]</span><span style="color:#ed9366;">.</span><span style="color:#f07171;">clone</span><span>()</span><span style="color:#61676ccc;">;
</span></code></pre>
<p><br><br>
And that's about it. What's your experience with type conversion in Rust?
<br><br></p>
<p>Attribution: Article image by <a href="https://pixabay.com/users/ulleo-1834854/?utm_source=link-attribution&utm_medium=referral&utm_campaign=image&utm_content=1666499">Ulrike Leone</a> from <a href="https://pixabay.com//?utm_source=link-attribution&utm_medium=referral&utm_campaign=image&utm_content=1666499">Pixabay</a></p>
Using Google Fonts in a GDPR compatible way2022-11-20T12:29:16+00:002022-11-20T12:29:16+00:00https://bgrande.de/blog/using-google-fonts-in-a-gdpr-compatible-way/<p>Recently there was a court ruling in Germany making Google Fonts an issue with GDPR. I wrote a script to detect and replace its usage in a compliant way.</p>
<span id="continue-reading"></span><br>
<h2 id="gdpr-and-google-fonts">GDPR and Google Fonts</h2>
<p>The <a href="https://gdpr-info.eu/">General Data Protection Regulation</a>, also known as GDPR, DSGVO (Datenschutzgrundverordnung - you gotta love German...) is a double-edged sword.
On the one hand it brought more power to the users of web applications and pages, on the other hand, in many cases made life unnecessarily complicated for many small website or webapp operators.<br>
You probably had your fair share with those infamous Cookie Banners.
<picture>
<source media="(min-width: 800px)" srcset="https://bgrande.de/processed_images/cookies.7f8f607ddb364be4.webp" type="image/webp" />
<source media="(min-width: 800px)" srcset="https://bgrande.de/processed_images/cookies.2f9e557a2b435af9.jpg" type="image/jpeg" />
<source media="(min-width: 600px)" srcset="https://bgrande.de/processed_images/cookies.b5096335de635748.webp" type="image/webp" />
<source media="(min-width: 600px)" srcset="https://bgrande.de/processed_images/cookies.19e4b6bd67077ccb.jpg" type="image/jpeg" />
<source media="(min-width: 0px)" srcset="https://bgrande.de/processed_images/cookies.27e73b832da7573a.webp" type="image/webp" />
<source media="(min-width: 0px)" srcset="https://bgrande.de/processed_images/cookies.21ca92e1b811b404.jpg" type="image/jpeg" />
<img src="https://bgrande.de/processed_images/cookies.19e4b6bd67077ccb.jpg"
itemprop="picture"
srcset="https://bgrande.de/processed_images/cookies.19e4b6bd67077ccb.jpg, https://bgrande.de/processed_images/cookies.2f9e557a2b435af9.jpg 2x"
alt="Not a cookie banner - but delicious cookies - yummy"
width="1024"
height ="576"
loading=lazy
/>
</picture>
Ok, well, not the cookies I was talking about... Sorry, I have been a bit distracted. So... Yummy...</p>
<p>Anyway, where was I? </p>
<p>So it should be no surprise now, that a <a href="https://www.theregister.com/2022/01/31/website_fine_google_fonts_gdpr/">German court ruled against using Google Fonts</a> at the beginning of 2022:</p>
<blockquote>
<p>The unauthorized disclosure of the plaintiff's dynamic IP address by the defendant to Google constitutes a violation of the general right of personality in the form of the right to informational self-determination according to § 823 Para. 1 BGB</p>
</blockquote>
<p>While this is definitely true, the implications the ruling might have in the future, in my opinion, are going too far. </p>
<p>I'm not a lawyer, so take this with a grain of salt, but:<br>
As a user and provider of external resources you might face some serious issues if you think about it further.
Like using a CDN, images and whatnot via 3rd party. </p>
<p>It's probably aimed more to be an issue for the bigger players here.
Although, this is essentially how the web works nowadays.
You load 3rd party resources all the time.
While using 3rd party resources without checking can have security implications as well, many websites and apps use services like this, especially no-code tools.</p>
<p>I can already see the Cookie Banner becoming an even larger Consent Banner (which it sometimes already is) for all the services, essential or not.</p>
<p>That being said: I'm not inherently against GDPR. I think it brings a good set of features to get people more privacy aware and at the same time put some restrictions in place against companies like Facebook (well, Meta now) or Google.
Although, those big companies seemed to be able to avoid most of the restrictions for quite some time because they had the power to force the users into consent.
It seems to me this has changed at least a bit lately so that's a good thing.
Also, as a web-developer you can now have GDPR compliance as a unique selling point. To the more privacy-savvy people at least. </p>
<h3 id="so-why-replacing-google-fonts-for-gdpr-now">So why replacing Google Fonts for GDPR now?</h3>
<p>Recently some law firms in Germany started sending notices, again. Especially to real estate companies.
I happen to have a customer like this, so I had to clean up the page before they were in trouble.</p>
<p>Which after some adjustments for their codebase lead me into creating a more elaborated script doing the job.<br />
<br><br></p>
<h2 id="the-google-font-replacement-script">The Google Font replacement script</h2>
<p>The <a href="https://github.com/bgrande/gfontreplacer" title="External link to GitHub project gfontreplacer">replacement script</a> is written in PHP and scanning PHP, CSS and JS files in default mode, while you can extend the filters via parameters.</p>
<p>The current features are:</p>
<ul>
<li>Search a code base (<code>targetpath</code>) for the usage of Google Fonts </li>
<li>Download the found fonts (or CSS files including the font files)</li>
<li>Backup the files where the font has been found</li>
<li>Replace the found paths with the downloaded files</li>
<li>Print some stats about how many files and fonts found
<br><br></li>
</ul>
<h2 id="future-plans">Future Plans</h2>
<p>Based on the code scanning heuristic and some additional logic I have plans to extend this tool into a GDPR compatibility scanner.
I had this plans for about a year now in my head, and it might be a fun project. The idea is to scan the webpage for possible GDPR issues like using Google Fonts as well as providing an executable for scanning the code to uncover possibly hidden GDPR issues as well as providing a solution, i.e. by replacing the used fonts.
<br><br><br></p>
<h2 id="image-attribution">Image Attribution</h2>
<p>The article image used at top of the article: Image by <a href="https://pixabay.com/users/mohamed_hassan-5229782/?utm_source=link-attribution&utm_medium=referral&utm_campaign=image&utm_content=3256079">Mohamed Hassan</a> from <a href="https://pixabay.com//?utm_source=link-attribution&utm_medium=referral&utm_campaign=image&utm_content=3256079">Pixabay</a>
The cookie image: Photo by <a href="https://unsplash.com/@shootdelicious?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Eiliv Aceron</a> on <a href="https://unsplash.com/s/photos/cookie?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a></p>
How current events inspired me to create arewefuckedyet2022-06-29T10:36:14+00:002022-06-29T10:36:14+00:00https://bgrande.de/blog/how-current-events-inspired-me-to-create-arewefuckedyetcom/<p>The war in Ukraine and the everlasting climate change issue made me think about how fucked we as a species really are.
So I got the idea of creating a website to document our species' progress on that matter. </p>
<span id="continue-reading"></span><br>
<h2 id="why-did-i-create-arewefuckedyet">Why did I create arewefuckedyet?</h2>
<p>The war started by Russia in Ukraine made me have this idea of creating a page telling the <a href="https://arewefuckedyet.com">current stage of the world</a> in a satirical way.</p>
<p>It's partly funny, but also offers a lot of doomsday scenarios (including the doomsday clock).
Also, there are some hints about ideas on how to solve at least some of the problems we humans are currently facing. Including interesting videos, books and apps.
Ah, and also what I think about all of this.</p>
<p><img src="https://bgrande.de/blog/how-current-events-inspired-me-to-create-arewefuckedyetcom/doomsday-clock.png" alt="arewefuckedyet.com's doomsday clock" title="arewefuckedyet.com's doomsday clock" /></p>
<p>Visually the landing page is based on the <a href="https://thebulletin.org/doomsday-clock/">doomsday</a> clock you might already know (or not).</p>
<p>The page was online for a few months so far. Finally, I got the time to share a bit more information about it including the tech I used to create the page.
<br><br></p>
<h2 id="how">How</h2>
<p>Under the hood, I used a few simple tools and scripts to publish the page.
<br></p>
<h3 id="doomsday-clock">Doomsday clock</h3>
<p>At first, I started scraping the doomsday clock page. </p>
<pre data-lang="javascript" style="background-color:#fafafa;color:#61676c;" class="language-javascript "><code class="language-javascript" data-lang="javascript"><span style="color:#fa6e32;">const </span><span>{ groups: parsed } </span><span style="color:#ed9366;">= </span><span>text</span><span style="color:#ed9366;">.</span><span style="color:#f07171;">match</span><span>(</span><span style="color:#4cbf99;">/(?<</span><span>sentence</span><span style="color:#4cbf99;">></span><span style="color:#ff8f40;">.</span><span style="color:#ed9366;">*</span><span style="color:#4cbf99;">: )</span><span style="color:#ed9366;">?</span><span style="color:#4cbf99;">IT IS(?: STILL)</span><span style="color:#ed9366;">? </span><span style="color:#4cbf99;">(?<</span><span>time</span><span style="color:#4cbf99;">></span><span style="color:#ff8f40;">\d</span><span style="color:#ed9366;">+</span><span style="color:#4cbf99;">)(?: AND A (?<</span><span>half</span><span style="color:#4cbf99;">>HALF))</span><span style="color:#ed9366;">? </span><span style="color:#4cbf99;">(?<</span><span>type</span><span style="color:#4cbf99;">>MINUTES</span><span style="color:#ed9366;">|</span><span style="color:#4cbf99;">MINUTE</span><span style="color:#ed9366;">|</span><span style="color:#4cbf99;">SECONDS</span><span style="color:#ed9366;">|</span><span style="color:#4cbf99;">SECOND) TO MIDNIGHT/</span><span style="color:#fa6e32;">i</span><span>)
</span><span style="color:#fa6e32;">const </span><span>seconds </span><span style="color:#ed9366;">= </span><span>parsed</span><span style="color:#ed9366;">.</span><span>type </span><span style="color:#ed9366;">=== </span><span style="color:#86b300;">"seconds" </span><span style="color:#ed9366;">? </span><span style="font-style:italic;color:#55b4d4;">Number</span><span>(parsed</span><span style="color:#ed9366;">.</span><span>time) </span><span style="color:#ed9366;">: </span><span>(</span><span style="font-style:italic;color:#55b4d4;">Number</span><span>(parsed</span><span style="color:#ed9366;">.</span><span>time) </span><span style="color:#ed9366;">* </span><span style="color:#ff8f40;">60</span><span>) </span><span style="color:#ed9366;">+ </span><span>(parsed</span><span style="color:#ed9366;">.</span><span>half </span><span style="color:#ed9366;">? </span><span style="color:#ff8f40;">30 </span><span style="color:#ed9366;">: </span><span style="color:#ff8f40;">0</span><span>)
</span></code></pre>
<br>
<h3 id="the-page">The page</h3>
<p>It's a simple static HTML page using <a href="https://daisyui.com/">daisyUI</a> for layout and styling.
<img src="https://bgrande.de/blog/how-current-events-inspired-me-to-create-arewefuckedyetcom/its-simple.jpg" alt="It's simple meme with toy story" title="It's simple meme with toy story" />
<br><br></p>
<h3 id="publishing">Publishing</h3>
<p><img src="https://bgrande.de/blog/how-current-events-inspired-me-to-create-arewefuckedyetcom/github-pages.png" alt="Screenshot of GitHub Pages connected to arewefuckedyet.com" title="Screenshot of GitHub Pages connected to arewefuckedyet.com" /></p>
<p>For publishing the page I used GitHub Pages. This means the page is in a public <a href="https://github.com/bgrande/arewefuckedyet.com">GitHub repository</a>.
PRs are welcome if you have an idea, fix or contribution.
<br><br></p>
<h3 id="deployment">Deployment</h3>
<p>Here we go with GitHub Actions, again.
I used <code>JamesIves/github-pages-deploy-action</code> to deploy the page without using jekyll.</p>
<p>Also, with GitHub Actions the doomsday clock parser gets triggered daily to provide the latest updates.
After that, a new version will be deployed, of course.
<img src="https://bgrande.de/blog/how-current-events-inspired-me-to-create-arewefuckedyetcom/github-action.png" alt="Screenshot of GitHub Action deployment to arewefuckedyet.com" title="Screenshot of GitHub Action deployment to arewefuckedyet.com" />
<br><br></p>
<h3 id="domain-hosting">Domain hosting</h3>
<p>Domain hosting is done via namecheap, which just needs <code>A</code> entries for GitHub Pages servers.
<br><br></p>
<h3 id="user-poll">User poll</h3>
<p><img src="https://bgrande.de/blog/how-current-events-inspired-me-to-create-arewefuckedyetcom/the_general_problem-overengineering.png" alt="Overengineering from XKCD" title="Image source https://xkcd.com/974" /></p>
<p>For testing <a href="/blog/php-with-superpowers/">PHP swoole</a> in combination with file-based data storage and testing nginx reverse proxy, I wrote a simple yes/no poll service.
Using that service users can vote on what they think about humanity's current stage (are we fucked -> yes or no).
The poll's results are part of the landing page's welcoming message.
<br><br></p>
<h3 id="affiliates">Affiliates</h3>
<p>I always wanted to test Amazon's affiliate system, so I took this project and added some books about arewefuckedyet's topics with affiliate links.
Since they're at the end of the page, and I haven't done any real marketing so far this didn't make a difference but at least I know how the affiliate program works, now.
<br><br></p>
<p>That's it, and now have fun reading <a href="https://arewefuckedyet.com">arewefuckedyet.com</a> and leave your vote! </p>
Interesting articles about innovation, money and work2022-05-04T11:00:12+00:002022-05-04T11:00:12+00:00https://bgrande.de/blog/interesting-articles-about-innovation-and-money/<p>Here are some of the most interesting articles about innovation, money and work I read the last 2 weeks.
<br><br></p>
<h2 id="innovation">Innovation</h2>
<p>Should you start from scratch when building something? Or do innovations come from iteration?
That's what James Clear writes about in his article <a href="https://jamesclear.com/dont-start-from-scratch">Don't start from scratch</a>.
Summing it up: No, most of the time you shouldn't start from scratch! Most innovation comes from iterating on or building on already existing technologies or solutions.
<br><br></p>
<h2 id="money">Money</h2>
<p>You probably already thought about the question: <em>How much money is enough?</em>.
Turns out there's an answer for that.
It's an individual one for each of us. And it might not be what you think it is.
Having not enough money sucks. Having too much can suck as well.
So there's not a threshold but a range of wealth where you can truly do and say what you want.
Hence, <a href="https://www.zerohedge.com/personal-finance/fk-you-money">f**ck you money</a>.
<br><br></p>
<h2 id="work">Work</h2>
<p>Essentially, this article <a href="https://www.fastcompany.com/90742937/how-to-use-daily-quadrants-to-get-more-done-each-day">how to use daily quadrants is</a> about when to be most effective and to do what kind of work.
In the end: do the heavy (brain) lifting in the morning when you're more focused and alert.
<br><br>
Also, what about the work as a developer: What do you think about Pull Requests?
Are they a good thing for your professional work or could they <a href="https://betterprogramming.pub/are-pull-requests-holding-back-your-team-e8aec48986c2">hold you back</a>?</p>
<p>There's evidence stating that high performance teams do not seem to use PRs quite often.
Which might either be because they just don't need it since they're just a well oiled team who found their ideal workflows and processes.
Or that those teams might not profit from using PRs at all. </p>
<p>The article tends to conclude that it works well for managing trust i.e. in Open Source development teams. </p>
<p>But professional teams working in a company shouldn't use this additional step.
At least not if they want to be a fast moving team producing rapid results. </p>
Articles about Rust for OpenCV and a Svelte Store2022-05-04T10:10:12+00:002022-05-04T10:10:12+00:00https://bgrande.de/blog/articles-about-rust-for-opencv-and-svelte-store/<p>If you're into Rust and web development here are some interesting Rust related articles I've read lately.
<br><br></p>
<h2 id="rust-and-opencv">Rust and OpenCV</h2>
<p>If you're interested in Rust and missing image recognition features like with OpenCV this article <a href="https://blog.devgenius.io/rust-and-opencv-bb0467bf35ff">about Rust with OpenCV</a> got you covered.
<br><br></p>
<h2 id="rust-and-svelte-store">Rust and Svelte Store</h2>
<p>So, I like developing with Svelte and Rust. When I found this article about <a href="https://daveceddia.com/svelte-store-in-rust/">developing a Svelte store with Rust</a> I was instantly hooked.
<br><br></p>
<h2 id="effective-rust">Effective Rust</h2>
<p>Do you know the book <em>Effective C++</em>?
It's quite popular in the C++ world and if you enjoyed it and are about to learn Rust you might like the book <a href="https://www.lurklurk.org/effective-rust/">Effective Rust</a>. </p>
Is there anything good about feedback at work?2022-03-31T13:56:12+00:002022-03-31T13:56:12+00:00https://bgrande.de/blog/is-there-anything-good-about-feedback-at-work/<p>I happened to stumble upon this article from 2019 about "<a href="https://hbr.org/2019/03/the-feedback-fallacy">the feedback fallacy</a>", again.
If you don't know it, it's a very good read!</p>
<p>You might have asked yourself if there was anything good about those feedback rounds.
This article pretty much sums this up and handles the good and especially the bad parts.<br />
And makes suggestions on how to do it better.
<br><br></p>
<h2 id="my-takeaway">My takeaway</h2>
<ol>
<li>Always handle feedback like an opinion and present it like that</li>
<li>Concentrate on strengths and how to connect the strengths with possible weaknesses</li>
<li>Direct and humble feedback is worth more than general feedback rounds</li>
<li>The receiver (of feedback) decides if they should act on it or not</li>
<li>A weakness to one can be a strength to the other. And the other way around. Also, this might very much depend on the situation or job. Meaning: There rarely is one and only truth</li>
</ol>
Using github actions to deploy via ssh or scp2022-02-03T16:56:12+00:002022-02-03T16:56:12+00:00https://bgrande.de/blog/github-actions-ssh/<p>This article is part of a new format called <strong>shorts</strong>.
Here I will just share one or a few links, maybe an interesting video or podcast.
Or some outlines about what I learned today.<br />
<br></p>
<h2 id="today-i-learned">Today I learned</h2>
<p>If you're about to use GitHub actions to deploy, there's a perfect blog article by Zell Liev about getting <a href="https://zellwk.com/blog/github-actions-deploy/">scp and ssh to work</a> in GitHub actions.</p>
How to simplify your job application in 20222022-02-01T14:40:54+00:002022-02-01T14:40:54+00:00https://bgrande.de/blog/how-to-simplify-your-job-application-in-2022/<p>To reduce the amount of recruiting messages I wrote a questionnaire tool (retrap) recruiters can use to see if it makes sense to write to me.
But what do you do once you get an interesting offer and time still matters?</p>
<span id="continue-reading"></span><h2 id="how-to-filter-recruitment-offers">How to filter recruitment offers</h2>
<p>Personally, I got a lot of recruitment offers which often do not even remotely match my interests.</p>
<p>To filter these emails and narrow potential communication down to at least theoretically interesting job offers I wrote a simple web app called <a href="https://github.com/bgrande/retrap">retrap</a>.</p>
<p>Well and for flexing my ideation to production performance skills.
<br><br></p>
<h3 id="what-s-it-all-about">What's it all about?</h3>
<p>The tool is made for tech-savvy devs who want to save some time with answering recruiting requests. With this tool, you just write a bunch of json code including questions for the recruiters and possible answers. </p>
<picture>
<source media="(min-width: 800px)" srcset="https://bgrande.de/processed_images/questions_json.755111e7d14ddb0b.webp" type="image/webp" />
<source media="(min-width: 800px)" srcset="https://bgrande.de/processed_images/questions_json.a0aaa00555a08a49.png" type="image/png" />
<source media="(min-width: 600px)" srcset="https://bgrande.de/processed_images/questions_json.f4579b085bdb32c6.webp" type="image/webp" />
<source media="(min-width: 600px)" srcset="https://bgrande.de/processed_images/questions_json.ca33a1f416eb446a.png" type="image/png" />
<source media="(min-width: 0px)" srcset="https://bgrande.de/processed_images/questions_json.272ec4a89b3dfea4.webp" type="image/webp" />
<source media="(min-width: 0px)" srcset="https://bgrande.de/processed_images/questions_json.2d7d9f0558495595.png" type="image/png" />
<img src="https://bgrande.de/processed_images/questions_json.404276502eda525b.jpg"
itemprop="picture"
srcset="https://bgrande.de/processed_images/questions_json.404276502eda525b.jpg, https://bgrande.de/processed_images/questions_json.c9dfe582780e6bfe.jpg 2x"
alt="The json formatted questions for retrap"
width="1024"
height ="576"
loading=lazy
/>
</picture>
<p>A question could be i.e. "What's the salary range?". </p>
<p>Where possible answers would be sth. like ">=20k", ">=40k" or ">=50k". Each answer gets a weight now (the more important it is for you the higher the weight should be).</p>
<p>Now, when a recruiter answers the questions, the app will calculate the weight and based on a given threshold present an answer. Either turning it down, communicating I might be interested or probably thinking it was very interesting. </p>
<p>By configuring an email address of your own you will be noticed about each request simultaneously. The data will be saved via random (almost impossible to guess hashed string) name on your webserver where the email should contain a link to.
That's basically it. </p>
<p>In addition, there are some statistics, so you can analyze the responses. </p>
<p>The name is an abbreviation of Recruiter and Trap or RecruiterTrap. Which I derived from the well-known IT security feature <a href="https://en.wikipedia.org/wiki/Honeypot_(computing)">Honeypot</a>.
<br><br></p>
<h2 id="what-to-do-with-interesting-offers">What to do with interesting offers?</h2>
<p>Now, let's say there was an interesting offer or even multiple offers.
What are you going to do now? Obviously, most of the time, you have to write a job application including a resume. </p>
<p>But how can you simplify the process? Turns out there are some web apps simplifying the process for you. One of these tools, which I think is great, is <a href="https://resumey.pro/">Resumey.Pro</a>.
<br><br></p>
<h3 id="how-to-create-a-resume">How to create a resume</h3>
<p>Creating a resume can take quite some time, especially when you want to make sure that you'll get noticed. </p>
<p>Things like a clean and consistent layout, clear communication of your skills and hierarchy are some of the most important things you have to take care of.
Besides your actual curriculum, of course. </p>
<p>I really like the nice and clean design of Resumey.Pro's website! The landing page is well-structured.
The hero and copy deliver the message instantly: Use our app to get a way better chance with your job application!</p>
<p>So, how does it work?
<br><br></p>
<h3 id="edit-the-resume">Edit the resume</h3>
<figure>
<picture>
<source media="(min-width: 800px)" srcset="https://bgrande.de/processed_images/edit-resume.7dfd045be12a139d.webp" type="image/webp" />
<source media="(min-width: 800px)" srcset="https://bgrande.de/processed_images/edit-resume.b512a7e64499f29d.png" type="image/png" />
<source media="(min-width: 600px)" srcset="https://bgrande.de/processed_images/edit-resume.62d0cbbf3a84cb82.webp" type="image/webp" />
<source media="(min-width: 600px)" srcset="https://bgrande.de/processed_images/edit-resume.29157702d16efa9f.png" type="image/png" />
<source media="(min-width: 0px)" srcset="https://bgrande.de/processed_images/edit-resume.8f1d442fc97c134c.webp" type="image/webp" />
<source media="(min-width: 0px)" srcset="https://bgrande.de/processed_images/edit-resume.dc8b2bc018de4a4d.png" type="image/png" />
<img src="https://bgrande.de/processed_images/edit-resume.2632271bb6538a97.jpg"
itemprop="picture"
srcset="https://bgrande.de/processed_images/edit-resume.2632271bb6538a97.jpg, https://bgrande.de/processed_images/edit-resume.291847c94ad38348.jpg 2x"
alt="Resumey.pro's content editing mode"
width="1024"
height ="576"
loading=lazy
/>
</picture>
<figcaption>
Resumey.pro's content editing mode
</figcaption>
</figure>
<p>As you can see on the image it requires <a href="https://en.wikipedia.org/wiki/Markdown">Markdown</a> formatted text to produce the resume's content.
There's not much to say here since it's basically writing Markdown writing down your curriculum. If you don't know much Markdown you can use <code>/</code> to find the needed Markdown code like <code>/bold</code>.</p>
<p>After finishing the most important steps you can create the resume.
<br><br></p>
<h3 id="design-the-resume">Design the resume</h3>
<p>Now, you have a resume and you sure want it to be somewhat pretty. So in the next step, you can also (see picture above) use different colors, fonts and font size. to improve the look of your resume. </p>
<p><figure>
<picture>
<source media="(min-width: 800px)" srcset="https://bgrande.de/processed_images/design-resume.46a269414313c3ad.webp" type="image/webp" />
<source media="(min-width: 800px)" srcset="https://bgrande.de/processed_images/design-resume.c813ccc12c831024.png" type="image/png" />
<source media="(min-width: 600px)" srcset="https://bgrande.de/processed_images/design-resume.03e3b4593bcbf701.webp" type="image/webp" />
<source media="(min-width: 600px)" srcset="https://bgrande.de/processed_images/design-resume.abb622cc2e92e704.png" type="image/png" />
<source media="(min-width: 0px)" srcset="https://bgrande.de/processed_images/design-resume.1e223873db969727.webp" type="image/webp" />
<source media="(min-width: 0px)" srcset="https://bgrande.de/processed_images/design-resume.5464ec95f7954cbe.png" type="image/png" />
<img src="https://bgrande.de/processed_images/design-resume.2b65fc336d7cdbac.jpg"
itemprop="picture"
srcset="https://bgrande.de/processed_images/design-resume.2b65fc336d7cdbac.jpg, https://bgrande.de/processed_images/design-resume.2efe7e7df412f0bf.jpg 2x"
alt="Resumey.pro's design mode"
width="1024"
height ="576"
loading=lazy
/>
</picture>
<figcaption>
Resumey.pro's design mode
</figcaption>
</figure>
<br><br></p>
<h3 id="cover-letter">Cover letter</h3>
<p>Wait, you say, your job application surely needs a cover letter! Well, that's also possible. So, you can create, design and edit your complete application via resumey.pro including a cover letter. </p>
<p><figure>
<picture>
<source media="(min-width: 800px)" srcset="https://bgrande.de/processed_images/edit-coverletter.29beebc0cce049b7.webp" type="image/webp" />
<source media="(min-width: 800px)" srcset="https://bgrande.de/processed_images/edit-coverletter.31a6ebf802968f31.png" type="image/png" />
<source media="(min-width: 600px)" srcset="https://bgrande.de/processed_images/edit-coverletter.e1fcc2d9ee6f3ea1.webp" type="image/webp" />
<source media="(min-width: 600px)" srcset="https://bgrande.de/processed_images/edit-coverletter.dd644fa7270ad42c.png" type="image/png" />
<source media="(min-width: 0px)" srcset="https://bgrande.de/processed_images/edit-coverletter.465b08701302e438.webp" type="image/webp" />
<source media="(min-width: 0px)" srcset="https://bgrande.de/processed_images/edit-coverletter.2d6034d7ccbcb5b6.png" type="image/png" />
<img src="https://bgrande.de/processed_images/edit-coverletter.450c223e1562e3dc.jpg"
itemprop="picture"
srcset="https://bgrande.de/processed_images/edit-coverletter.450c223e1562e3dc.jpg, https://bgrande.de/processed_images/edit-coverletter.68fec674a68744c3.jpg 2x"
alt="Resumey.pro's coverletter creation"
width="1024"
height ="576"
loading=lazy
/>
</picture>
<figcaption>
Resumey.pro's cover letter creation
</figcaption>
</figure>
<br><br></p>
<h3 id="conclusion">Conclusion</h3>
<p>This project is nicely built, has a very simple layout and interface with easy to use features. It's a plus to know markdown but it's not a must. Thus, if you’re about to apply for a job, give Resumey.pro a try!</p>
How to manage passwords2021-12-03T10:35:02+00:002022-02-09T16:54:50+00:00https://bgrande.de/projects/how-to-manage-passwords/<p>Passwords are one of my favourite topics! There's so much you can do wrong when working with passwords. Here are some best practices on how to manage passwords.</p>
<span id="continue-reading"></span><br>
<h2 id="why-passwords">Why passwords?</h2>
<p>I already wrote a <a href="https://bgrande.de/blog/uber-den-guten-und-nicht-so-guten-umgang-mit-passwortern" title="Internal link to Password best practices blog article in German">blog article about password best practices</a> (in German) a few years ago. Most of the information here can be found in that article in detail.</p>
<p>In this post, I will briefly cover the whys and hows of passwords and best practices, while also offering links to the infographics I created.</p>
<p>So why passwords?</p>
<p>Mainly to authenticate a user against a service or app. You give your secret, the app saves it (hopefully hashed and salted) and on each login, it will be checked.</p>
<p>This leads to three very important conclusions:</p>
<ol>
<li>Passwords should be hard to guess</li>
<li>Passwords should be stored as salted hash so they can't be recovered within a reasonable amount of time</li>
<li>The same password should not be used for different services multiple times</li>
</ol>
<p>There are a few more to consider when developing an app using authentication. For that reason, I created an infographic for developers you can find in the following section.
<br><br></p>
<h3 id="tl-dr">TL;DR</h3>
<p>Consider using the <a href="https://bgrande.de/projects/how-to-manage-passwords/how-to-manage-passwords-in-your-application-as-a-developer-infographic.pdf" title="PDF document How to manage passwords in your application for developers infographic">How to manage passwords in your application infographic's</a> best practice advice.</p>
<p>Also, as a user, please read the <a href="https://bgrande.de/projects/how-to-manage-passwords/how-to-securely-manage-passwords-as-a-user-infographic.pdf" title="PDF document How to securely manage passwords for users infographic">How to securely manage passwords infographic</a>.</p>
<p>Feel free to reach out to me if you have questions or disagree.
<br></p>
<h3 id="update">Update</h3>
<p>In a former version I concluded that it would be always best to set the minimum required length for passwords to at least 12 characters. </p>
<p>When I published these infographics someone pointed out this <a href="https://techcommunity.microsoft.com/t5/azure-active-directory-identity/your-pa-word-doesn-t-matter/ba-p/731984">Microsoft research about passwords</a>.
After reading a bit more about different viewpoints I mostly agree with their conclusion that in many cases the password's length isn't that important as long as it's about 8 characters long.</p>
<p>This is for two reasons:</p>
<ol>
<li>Most password breaches are done via methods other than breaking passwords via <em>bruteforce</em></li>
<li>Some (too many) users might get confused by requiring a password longer than 8 chars (thus, for the same reasons as not requiring special characters, numbers and so on, the resulting password may be less secure)</li>
</ol>
<p>This is why I changed the Infographics a bit to better reflect that fact recommending at least 8 characters for an application implementation now instead of 12.
Although, depending on the target audience (more tech-savvy), I think you can still go with 12 in some cases.
<br><br></p>
<h2 id="alternatives-or-completive-measures">Alternatives or completive measures</h2>
<p>There are three different methods for authentication that can be used in combination with each other for greater security:</p>
<ul>
<li>Something you have (like a keycard, FIDO, App, ...)</li>
<li>Something you know (like a password, passphrase, also one-time passwords)</li>
<li>Something you are (like fingerprints, iris, ...)
<br>
So we already got the password (something you know) covered. As an additional factor (2FA/mFA) you should add something you have like an Authenticator app on your mobile phone or a security stick like <a href="https://fidoalliance.org/" title="External link to fido alliance">FIDO</a>.</li>
</ul>
<p>You could also add i.e. a fingerprint. Although, in my opinion, this is somewhat more delicate if it will be stored somewhere online. If there was a breach, hackers would be able to <a href="https://www.wired.com/2012/07/reverse-engineering-iris-scans/" title="External link about iris scan reverse engineering">reverse engineer even irises</a>. </p>
<p>Also, a fingerprint can be physically copied and used on your behalf. So with one breach all (or at least some) of your "haves" could be broken for authentication forever.
<br><br></p>
<h2 id="what-are-a-good-password-s-properties">What are a good password's properties?</h2>
<p>Aim for long passwords or passphrases. The longer the password the better the entropy. Special chars, numbers and upper/lowercase can help to improve the complexity and therefore entropy as well.</p>
<p>The most important trait of all: The password has to be unique to the current service. No cross-usage!</p>
<p>Also, the more random and the less obvious to guess (relatively easy to guess are words like password, date of birth or just repeating characters or words multiple times)
<br><br></p>
<h2 id="how-can-i-improve-my-user-s-experience">How can I improve my user's experience?</h2>
<p>Aim for a seamless and easy experience while guiding the user on what passwords are safe and why they should be! Throw in 2FA to further increase account security.
<br><br></p>
<h2 id="further-reading">Further reading</h2>
<ul>
<li><a href="https://auth0.com/blog/dont-pass-on-the-new-nist-password-guidelines/">NIST Password Guidelines and Best Practices for 2020</a></li>
<li><a href="https://pages.nist.gov/800-63-3/sp800-63b.html">NIST Special Publication 800-63B</a></li>
<li><a href="https://en.wikipedia.org/wiki/List_of_the_most_common_passwords">List of most common passwords</a></li>
<li><a href="https://www.microsoft.com/en-us/research/publication/password-guidance/">MS Password Guidance</a></li>
<li><a href="http://cups.cs.cmu.edu/passwords.html">Research Overview</a></li>
<li><a href="https://github.com/cupslab/password_meter">Password Strength Meter PoC github</a></li>
<li><a href="https://cups.cs.cmu.edu/meter/">Password Strength Meter PoC demo</a></li>
</ul>
Break-even calculation and plotting the result2021-11-12T11:54:48+00:002021-11-12T14:58:53+00:00https://bgrande.de/blog/breakeven-calculation-and-plotting-the-result/<p>Calculating the break-even is a helpful tool in determining if a venture or project has the potential to succeed. <br />
Here's how you do it and how to create an app simplifying the result's representation.</p>
<span id="continue-reading"></span>
<p>The <a href="https://en.wikipedia.org/wiki/Break-even_(economics)" title="external link to Wikipedia article about the break-even (point)">break-even</a> or break-even point is a tool used in economics to determine a project's potential.</p>
<p>It's often used in cost accounting and in my opinion very helpful (provided you know some numbers) in spotting if you're about to make some revenue. Or not!</p>
<p>Simply put, if your sales will be higher than the break-even point you're going to make a profit. If it's lower you're not.
<br><br></p>
<h2 id="calculating-the-break-even-point">Calculating the break-even point</h2>
<p>Calculating the break-even point is fairly easy. You need to know some numbers, though!</p>
<p>First, you need to know your fixed costs related to the product's creation.</p>
<p>This might be office rent, costs for internet and phone, machines and computers and more. Things that you have to pay for running your project (or business) with or without producing or selling the actual product. So you might have to summarize any costs related to the project to get the sum of it.</p>
<p>Second, you need to know the variable (or running) costs. These are related to each piece and include any materials you need to produce your product. Like provisions, the time you (or others) need to invest to create each piece or even to transport the product. Again, you need to do some summarizing here.</p>
<p>Third, you need a price. You can set this by yourself, or it might already be set. Nonetheless, it should be quite higher than the variable costs. If you also know the potential number of items you're going to sell you can derive the minimum price by using that number in relation to the full costs (adding the fixed and variable costs).</p>
<p>This brings me to the last point. It's really helpful to know the potential amount of units going to be sold!</p>
<p>In the end, the whole calculation can be set into a time relation. Like per month, per day or per year.</p>
<p>So, we got</p>
<ul>
<li>(total) fixed Costs <em>Cf</em></li>
<li>variable Costs <em>Cv</em></li>
<li>the Price <em>P</em></li>
<li>amount of units to be sold <em>Us</em></li>
</ul>
<p>Using these we can now calculate the break-even point via the following formula:</p>
<p><em>X = Cf / P - Cv</em></p>
<p>So what about the <em>Us</em>? This is where you're going to make your decisions! If the value is higher than the calculated break-even: Depending on how much, you're clear to go on.</p>
<p>If it's less or exactly the same you're not going to see any profit continuing this project. Either you're able to reduce the costs, increase the price or increase the numbers sold or you should stop the project.
<br><br></p>
<h2 id="creating-a-break-even-calculation-app">Creating a break-even calculation app</h2>
<p>Knowing this, we can start creating an app calculating and plotting the results into a diagram.</p>
<p>So, what do we need for an app? TLDR: <a href="https://bgrande.de/breakeven/" title="Internal link to the breakeven calculation application">try it yourself</a>!
<br><br></p>
<h3 id="chart-library">Chart library</h3>
<p>Some kind of diagram library. Over the past, I used quite a few. Namely, I started out using <a href="https://www.highcharts.com/" title="external link to highcharts website">highcharts</a>, later on, <a href="https://www.chartjs.org/" title="external link to chart.js website">chart.js</a>, <a href="https://gionkunz.github.io/chartist-js/" title="external link to chartist website">chartist</a> and very recently <a href="https://frappe.io/charts" title="external link to frappe charts website">frappe charts</a>. Staying with Open Source libraries for this project I tried <a href="https://apexcharts.com/" title="external link to apex charts website">apex charts</a>.</p>
<p>I consider all of these projects a good fit for creating charts but since apex brought a variety of different chart types and is based on SVG it seemed to be the best choice for this project. Also, out of the box, apex gives you exports for SVG and PNG downloads amongst zooming in and out in your dataset.
<br><br></p>
<h3 id="form">Form</h3>
<p>Next up, we need some form elements to get our input. To make it look pretty from the beginning I chose the <a href="https://tailwindcss.com/" title="external link to tailwindcss website">tailwindcss</a> based <a href="https://daisyui.com/" title="external link to daisyUI website">daisyUI</a>. Implementation
<br><br></p>
<h3 id="implementation">Implementation</h3>
<p>Well, this was mainly reading docs to include and use the libraries and writing markup.</p>
<p>And: I used modern JavaScript (ES6), so older browsers (I'm looking at you Internet Explorer!) won't be able to use it.</p>
<p>Here are a few things I learned and that might be useful for other projects as well:</p>
<ol>
<li>Using an XMLHttpRequest in <em>beforeunload</em> event context doesn't work in Chrome.</li>
<li>Using a fetch request in <em>beforeunload</em> context has to be sent very fast (obviously). As within around 10ms.</li>
<li>You do not need to use an input file when building your tailwindcss: <em>npx tailwindcss --no-autoprefixer -o ./dist/tailwind.css --minify</em>.</li>
<li>Never forget to handle possible <em>Infinity</em> results when doing calculations in JavaScript! Although it's obvious, your CPU won't like that...</li>
<li>Apex charts aren't just shiny but easy to use as well
<br><br></li>
</ol>
<h3 id="usage-logging">Usage logging</h3>
<p>Not directly related but nonetheless a useful tool to see who's using the app and how I decided to add some usage logging compatible with GDPR. I had some very simple and basic implementations for a usage log so I used the existing code and improved it a bit. So I changed it to using ES6 and the <em>fetch API</em> instead of <em>XMLHttpRequest</em>.</p>
<p>Also, I made the PHP based backend as well as the logging frontend able to handle multiple logs per request instead of sending a new request for each log as the old simple implementation did.</p>
<p>Why implement this yourself, you may ask, since there are quite a handful of already existing applications out there? Good point and the only answer I have for you: Because I wanted to try this and see how simple and efficient such an implementation could be. And, well, because I can 😎.</p>
Are Micro Applications a thing?2021-10-23T15:37:32+00:002021-10-24T13:51:42+00:00https://bgrande.de/blog/are-micro-applications-a-thing/<p>You may have heard the term 'Micro Service'. <br />
Shortly summarized it coins for a very small (as in features) application often providing an API that can be used by another app or service.<br />
So, what about when you add a simple frontend to the pile?<br /></p>
<span id="continue-reading"></span>
<p><br><br></p>
<h2 id="the-term-micro-application-micro-app">The term Micro Application (Micro App)</h2>
<p>Looking up the term <a href="https://en.wikipedia.org/wiki/Microapp"><strong>Micro App</strong> at Wikipedia</a> states a very light and partly narrowed explanation. To the author, turns out, it was mostly related to small mobile apps with very specific requirements.</p>
<p>Other sources tend to define it <a href="https://www.progress.com/blogs/what-is-a-microapp">more closely related</a> to <a href="https://en.wikipedia.org/wiki/Microservices">Micro Services</a>. I tend to agree here and like to lay out some key features about what would qualify a <strong>Micro Application</strong> or <strong>Micro App</strong>:</p>
<ul>
<li>Highly focused on performing one task</li>
<li>Independently usable application</li>
<li>Easily replaceable by another implementation</li>
<li>Can easily be integrated into a collection of applications or a website</li>
<li>Provides a User Interface (frontend)</li>
</ul>
<p><em>Highly focused on performing one task</em>? Sounds familiar you might say? And you're absolutely right!</p>
<p>This is one of the key principles of the <a href="https://en.wikipedia.org/wiki/Unix_philosophy">Unix Philosophy</a>: <em>Make each program do one thing well</em>.</p>
<p>One thing regarding web applications that seemed open for discussion:</p>
<p>Should a Micro App include a backend like API as well? Or should it just use an existing API and we solely rely on having a frontend to meet the terms?</p>
<p>My first thought was to handle this point flexibly. On second thought and because there's another term (<a href="https://levelup.gitconnected.com/micro-frontend-architecture-794442e9b325">Micro Frontend</a>) describing an approach where you build a micro frontend for some backend service.<br />
Well, our app would be a Micro Frontend then.</p>
<p>In conclusion, a <strong>Micro App</strong> really should include some simple backend (i.e. implementing a service orchestrator) and frontend to differentiate between a Micro Service and a Micro Frontend.</p>
<p>Therefore, as the last point to add to the above list, I would recommend: <em>Provides a backend used by the frontend</em>.
<br><br></p>
<h2 id="micro-application-feature-breakdown">Micro Application feature breakdown</h2>
<p>Most of the Micro App features are the same as for Micro Services. Outlined in the section before, here are the details. The following graphic roughly outlines the key differences between a monolithic and a micro approach to developing applications.
The comparison concentrates on having a frontend and backend as the main parts of each application, albeit these are not mutually exclusive.</p>
<p><img src="https://bgrande.de/blog/are-micro-applications-a-thing/micro-app-vs-monolithic-architecture.svg" alt="micro app vs monolithic architecture" title="A simple comparison of Monolith and Micro Apps" />
<br><br></p>
<h3 id="do-one-thing-and-do-it-well">Do one thing and do it well</h3>
<p>Technically, at least for a web service, we're doing two things here.</p>
<p>First, we have some kind of backend. And second, some kind of frontend.</p>
<p>In contrast to a Micro Service or a Micro Frontend, though, these two are supposed to be closely coupled. Following this route, we end up having a fully functional app with a UI. Importantly, only implementing one or a very basic set of features for a highly narrowed purpose.</p>
<p>This could be i.e. uploading an image via a web application.
<br><br></p>
<h3 id="independently-usable">Independently usable</h3>
<p>Here, we're aiming for having an app that doesn't need another application to function.</p>
<p>Keeping the uploading example from above, the app can be used for the single purpose of uploading an image to some server. Most likely as part of a set of apps where another app could take care of OCRing (<a href="https://en.wikipedia.org/wiki/Optical_character_recognition" title="external link to wikipedia article about optical character recognition">OCR</a> as in Optical Character Recognition) the image.
<br><br></p>
<h3 id="easily-replaceable">Easily replaceable</h3>
<p>Since, with our <strong>Micro App</strong>, we only implemented the feature of uploading an image we can easily replace this app by using another frontend framework. Maybe <a href="https://svelte.dev/" title="external link to svelte framework">svelte</a> instead of <a href="https://reactjs.org/" title="external link to react framework">react</a> 😁. This rewrite might only be a few hours of work then. Especially if you created detailed end-to-end tests beforehand. Additionally, it also allows testing (i.e. for performance) different implementations this way.
<br><br></p>
<h3 id="easy-to-be-integrated">Easy to be integrated</h3>
<p>Ok, this point is a bit more of a stretch. And IMHO a bit more of a loose feature.</p>
<p>That being said, a <strong>Micro App</strong> should allow for integration with a Single Sign-On (<a href="https://en.wikipedia.org/wiki/Single_sign-on" title="external link to wikipedia article about single sign-on">SSO</a>). As well as the connection to other features creating a feature-rich application composed out of multiple <strong>Micro Applications</strong>. Like combining the abovementioned <strong>uploading</strong>, <strong>OCRing</strong> and additionally <strong>viewing and editing</strong> the OCR results. Each via separate <strong>Micro Apps</strong>.
<br><br></p>
<h3 id="provides-a-user-interface">Provides a User Interface</h3>
<p>As described before, the app should provide an interface for the user to utilize the app.
<br><br></p>
<h3 id="provides-a-backend">Provides a backend</h3>
<p>Last but not least, we need some backend functionality so the user actions lead to a result. This is mainly providing the actual business case logic.
<br><br></p>
<h2 id="implementation-considerations">Implementation Considerations</h2>
<p>Basically, we have two (well, to keep it easy) options when creating an application:</p>
<p>Either we implement a <a href="https://en.wikipedia.org/wiki/Monolithic_application">Monolith</a> or a bunch of smaller applications. Although, there's always something in between and there's good reasoning for most of these solutions depending on the requirements.</p>
<p>While you would implement a Monolith as an application orchestrated by different modules, the <strong>Micro App</strong> approach can be used to implement each module as a separate application. With the Monolith, each module handles a specific feature or a small bunch of related features. With a <strong>Micro App</strong>, each program would handle such a feature or small set of features. Obviously, when using the monolithic approach you don't have to set up a new application (including boilerplate code) each time. Also, you can reuse existing code without having to include and take care of shared dependencies. At least regarding the code, you wrote for your business logic.</p>
<p>On the other hand, when having <strong>Micro Apps</strong> you can (best case) just create a new greenfield application implementing a new (or old) feature without having to rely on preexisting code. Therefore, with the <strong>Micro App</strong>, you can try new tech very easily. Especially for frontend solutions where (at least in the JavaScript ecosystem) frameworks come and go very fast, this can be a good way to stay on top of the trends.
<br><br></p>
<h2 id="advantages-of-using-the-micro-app-concept">Advantages of using the Micro App concept</h2>
<ul>
<li>Fast and easy to create</li>
<li>A single or small set of features to be handled in each app reduces each app's complexity</li>
<li>When using a preexisting backend the frontend can be easily replaced</li>
<li>Even when bundling frontend and backend new frameworks can be tested</li>
<li>The effort to build, deploy and test a single Micro App should be way less than within a monolithic app</li>
<li>Making and deploying changes is quite fast (development speed)</li>
<li>You can split apps and responsibilities between (smaller) teams</li>
<li>Apps can be glued together by another Micro App frontend</li>
<li>Better scalability</li>
<li>Single team responsibility</li>
<li>Easier and faster testing</li>
<li>smaller codebase per app
<br><br></li>
</ul>
<h2 id="disadvantages-of-using-the-micro-app-concept">Disadvantages of using the Micro App concept</h2>
<ul>
<li>More different repositories and applications to maintain</li>
<li>In many cases more boilerplate code</li>
<li>When having many common dependencies between features the dependencies have to be shared which further increases the number of packages to maintain</li>
<li>The effort for less complex applications or small teams can be quite high</li>
<li>Without a set of well maintained Continuous Deployment services you will run into a lot of issues and waste time
<br><br></li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>If you're a friend of Micro Services you probably already thought about an all micro approach to building apps. The Micro App approach has more or less the same (dis)advantages as the Micro Services. So, for bigger modularizable apps and teams it might be worth a try.</p>
<p>Although, if you're not careful, you might be about creating another Monolith... And here, ladies and gentlemen, we went full circle!</p>
<p>Last but not least: When creating an application, in my opinion, it's always good to keep in mind the Unix philosophy of making the app (or module or component) just do one thing right.
<br><br>
<strong>Article Image Attribution</strong>: Based on <a href="https://www.freepik.com/vectors/calendar">Calendar vector created by freepik - www.freepik.com</a></p>
Vor- und Nachteile des Homeoffice aka remote work während einer Pandemie2021-06-30T15:28:42+00:002021-08-30T21:05:12+00:00https://bgrande.de/blog/vor-und-nachteile-des-homeoffice-aka-remote-work-wahrend-einer/<p>Derzeit stoße ich auf einige Studien und Artikel, die sich des Themas Homeoffice zu Zeiten der Pandemie annehmen. <br />
Meiner Meinung nach sind diese häufig (in die eine oder andere Richtung) tendenziös, weshalb ich hier mit einigen Missverständnissen aufräumen möchte. </p>
<span id="continue-reading"></span><br>
<h2 id="homeoffice-fur-und-wider">Homeoffice Für und Wider</h2>
<p>Als Aufhänger diente für mich folgender <a href="https://www.xing.com/news/insiders/articles/home-office-es-ist-alles-noch-schlimmer-4100838" title="Externer Link auf Home Office - Alles noch schlimmer? Artikel von XING">Artikel auf XING</a>.</p>
<p>Meiner Meinung nach wird hier einiges durcheinander geworfen.</p>
<p>Man sollte hier die besonderen Umstände beachten. Hier wird, so wie ich es sehe, Remotearbeit während einer Pandemie mit der Büroarbeit währen "Normalzeiten" verglichen. Es sollte allerdings klar sein, dass die Remotearbeit während einer Pandemie nicht mit allgemeiner Remotearbeit vergleichbar ist.
<br><br></p>
<h3 id="eigene-erfahrung-mit-remotearbeit">Eigene Erfahrung mit Remotearbeit</h3>
<p>Ich würde mich als Vorpandemieremotearbeiter(TM) als gut ausgestattetet und stark selbstmotivierend und selbstorganisierend bezeichnen. Die Arbeit fand bis zur Pandemie in einem gemischten (Remote + Vor-Ort) Team statt. Sowohl vor als auch während der Pandemie gab es bei mir keine gesundheitlichen oder psychischen Probleme.</p>
<p>Allerdings ist die Belastung während der Pandemie stark gestiegen, während sie davor deutlich geringer als während der reinen Vor-Ort-Arbeit war.</p>
<p>In erster Linie durch die Betreuung der Kinder zu Hause. Aber auch durch die anfangs 1:1 auf Videotelefonie verlegten Meetings. Aus diesen Gründen und zusätzlich aufgrund der angespannten Gesamtsituation musste ich deutlich mehr Energie als bisher bei deutlich weniger Pausen aufbringen. Mir ist natürlich klar, dass ich als Einzelbeispiel nicht repräsentativ bin, allerdings weiß ich von einigen anderen Leuten mit ähnlichen Bedingungen, dass es ihnen weitestgehend genauso erging.
<br><br></p>
<h3 id="homeoffice-waehrend-der-pandemie">Homeoffice während der Pandemie</h3>
<p>Hier wird meiner Meinung nach schon recht offensichtlich, was ausschlaggebend für teilweise alarmierende Ergebnisse bei den genannten Studien war. Die deutlich erhöhte Belastung in Bezug auf Kommunikation, Kinderbetreuung und Alltagsorganisation, Arbeitszeitverdichtung, Verunsicherung und eine andauernd unsichere Ausnahmesituation kombiniert mit einer für die meisten ungewohnte Arbeitsweise mit häufig nicht ausreichender Ausstattung führt zwangsläufig zu vermehrt physischen und psychischen Erkrankungen.</p>
<p>Nebenbei ist erwartbar, dass in solch einer Situation die Arbeitszeit gegenüber dem Büro steigt. Das kann z. B. an vermehrter Kontrolle des Arbeitgebers, Jobunsicherheit, aber auch an häufigeren Unterbrechungen sowie unzureichenden Arbeitsbedingungen liegen. Zum anderen fällt die Pendelzeit weg, die nun teilweise als Ausgleich oder bei Alleinstehenden als Beschäftigung genutzt wird.</p>
<p>Demgegenüber stehen Studien, die der Remotearbeit selbst während der Pandemie eine <a href="https://www.personalwirtschaft.de/der-job-hr/arbeitswelt/artikel/unternehmen-erwarten-auch-nach-der-krise-steigerung-der-produktivitaet-durch-mobiles-arbeiten.html" title="Externer Link gesteigerte Produktivität Homeoffice">gesteigerte Produktivität zuschreiben</a>. Hier hätte ich eigentlich in jedem Fall eine Verringerung erwartet.</p>
<p>Aus diesem Grund würde ich die im XING-Artikel beschriebenen Probleme als weniger ernst in Bezug auf Remotework im Allgemeinen, sondern in Bezug auf Homeoffice im Zuge einer Pandemie ansehen.</p>
<p>Was nicht heißt, dass es nicht Probleme zu lösen gibt und eine gute Ausstattung für erfolgreiches von zu Hause arbeiten nicht unumgänglich ist. Außerdem sollten (auch im Büro) Meetings im Vorhinein auf ihre Notwendigkeit geprüft werden. Eine eins zu eins Übertragung von Meetings und persönlichen Treffen zu Videokonferenzen macht schon deshalb keinen Sinn, weil im Büro der direkte Kontakt meist effizienter und auch effektiver ist; die Vorteile der Remotearbeit kommen allerdings erst durch Nutzung der Asynchronität in Verbindung mit einer guten Dokumentation zu tragen. Kurz gesagt: Viele Meetings sind nicht notwendig und lassen sich durch (zeitlich getrenntes) Erarbeiten eines Themas oder durch kurze Chats viel besser erledigen.</p>
<p>Eine inklusive und offene Firmenkultur sollte außerdem auch bei Remotearbeit die offene Kommunikation und Kreativität erhalten. Hier gibt es in den meisten Fällen aus meiner Sicht sowieso einiges an Entwicklungspotential.
<br><br></p>
<h2 id="softwareentwickler-und-remotearbeit">Softwareentwickler und Remotearbeit</h2>
<p>Was mir im XING-Artikel ein bisschen aufstößt: Die Behauptung, Programmierer (die zähle ich im Weiteren Sinne mal zu den Softwareentwicklern) seien (Klischee, juhuu) meist Einzelgänger und daher auf kontaktloses bzw. remotearbeiten aus.</p>
<p>Mal abgesehen davon, dass es sich hierbei meiner Erfahrung nach tatsächlich um ein Klischee handelt, das von der Realität nur selten bestätigt wird, ist der Grund für eine Tendenz zum isolierten Arbeiten meist wie folgt:</p>
<p><strong>Softwareentwickler und Programmierer haben häufig sehr komplexe Probleme zu lösen, die lange und intensive Konzentrationsphasen notwendig machen. In dem Zusammenhang sei auch das Stichwort "Deep Work" erwähnt. Dies ist selbst in einem durchschnittlich ausgestatteten Homeoffice i.d.R. deutlich einfacher zu bewerkstelligen als im häufig lauten und von Unterbrechungen geprägten Büroalltag.</strong>
<br><br></p>
<h2 id="und-sonst-so">Und sonst so</h2>
<p>Bliebe noch das angesprochene Organisations- und Motivationsproblem: Fehlende Selbstmotivation und -Organisation dürfte auf Dauer auch im Büro ein Problem sein und liegt vermutlich wie so oft auch an der Firmenkultur. Natürlich auch an der Persönlichkeit des jeweiligen Individuums und häufig der Zufriedenheit mit der Jobwahl. Selbstorganisation und "Nein sagen" kann man lernen und üben. Die Selbstmotivation bis zu einem gewissen Grad auch, hier spielt die Firmenkultur und Anforderungen von Vorgesetzten (z. B. Micromanagement) aber auch eine große Rolle.
<br><br></p>
<h2 id="tendenzielle-berichterstattung-update-august-2021">Tendenzielle Berichterstattung (Update August 2021)</h2>
<p>Zumindest gefühlt findet in letzter Zeit (Juni, Juli, August 2021) eine tendenzielle Berichterstattung in den Medien zum Thema Homeoffice statt.</p>
<p>So soll "uns" laut Wirtschaftswoche das Homeoffice zumindest laut Überschrift <a href="https://www.wiwo.de/erfolg/homeoffice/arbeitspsychologe-erklaert-das-homeoffice-hat-uns-neurotisch-gemacht/27548200.html" title="Externer Link zu Wirtschaftswoche Artikel über Home-Office">neurotisch gemacht</a> haben. Liest man dann den Text, sieht die Erkenntnis des interviewten Psychologen zwar doch differenzierter aus... Allerdings wird gleich mal ziemlich verallgemeinernd mit der Behauptung begonnen, dass "generell"</p>
<blockquote>
<p>nach ein bis zwei Tagen die Zufriedenheit von Mitarbeitern im Homeoffice abnimmt.</p>
</blockquote>
<p>Das mag für die Mehrheit stimmen, schließlich wurden hier Studien durchgeführt, allerdings noch lange nicht für jedes Individuum. Das stellt der Psychologe auch später im Interview noch klar, und zählt auch die Vorteile der Remotearbeit auf, nur um dann wenig später (ich habe keine Ahnung, inwieweit hier redaktionell geschnitten wurde), wieder darauf hinzuweisen, dass es im Grunde für niemanden gut wäre, dauerhaft von zu Hause zu arbeiten.</p>
<p>Und dann wieder das Thema "Struktur, sozialer Kontakt, Klatsch und Tratsch". Kann mir mal jemand erklären, was Menschen mit Familie so machen, wenn ihnen zu Hause der soziale Kontakt fehlt? Reden die dort nicht miteinander? Haben die keine Freunde? Und was machen die so, wenn ihnen die Struktur fehlt? Keine Selbstmotivation? Vielleicht der falsche Job? Versteht mich nicht falsch, auf Singles und viele extrovertierte Menschen mag das ja alles zutreffen, aber definitiv nicht auf jeden (und vor allem nicht auf mich, auf den Klatsch kann ich verzichten, ich habe Freunde und Familie und kann mein Leben und den Alltag ziemlich gut selbst strukturieren). Sowohl den Tratsch als auch die Struktur kann man übrigens auch zu Hause von außen haben, wenn man die richtigen Tools und Methoden (z.B. <a href="https://www.vmware.com/topics/glossary/content/remote-first" title="Externer Link zur Definition von Remote First (englisch)">Remote first</a>) nutzt. Das Homeoffice soll "uns" darüber hinaus auch noch "dröger" gemacht haben ... Ich glaube eher, dass hier auch einige gemerkt haben, dass ihr aktueller Job eigentlich nicht so zu ihnen passt.</p>
<p>Teams sollen sich außerdem tendenziell gespalten haben ... Hier mangelt es wahrscheinlich wieder an den richtigen Methoden und es wurde offenbart, was davor schon nicht richtig funktioniert hatte...</p>
<p>Und dann, um auf die Neurosen zurückzukommen: Hier sagt der Interviewte, die Pandemie habe uns (also einige zumindest) neurotischer gemacht. Außerdem ängstlicher und unsicherer. Ah, ja, die Pandemie, da war doch was ... Vielleicht war es ja gar nicht das Homeoffice? Zumindest nicht allein? Vielleicht, wirklich nur vielleicht, spielt ja die Pandemie für die psychischen Auswirkungen eine viel größere Rolle als der Arbeitsort? Sollte man sogar solche Studien mal außerhalb von Pandemiezeiten durchführen?
<br><br></p>
<h2 id="fazit">Fazit</h2>
<p>Ich würde nicht anmaßen, so weit zu gehen, dass Remotearbeit das Nonplusultra für jeden und in jeder Situation ist. Das ist es mit Sicherheit nicht. Aber es ist ein sehr sinnvolles Tool um z. B. fokussiertes Arbeiten und eine bessere Work-Life-Balance zu ermöglichen. Hinzu kommt, dass der Wegfall des Pendelns zum einen Zeit und Stress spart und darüber hinaus ein Beitrag zum Umweltschutz sein kann. Orientiert man sich an Remote first, kann man als Firma sogar von einer besseren schriftlichen Dokumentation und weniger in unnötigen Meetings verbrachter Zeit profitieren. Nicht zuletzt verringert die Nutzung des Homeoffice die Verbreitung von Infektionskrankheiten.</p>
<p>Wie so oft entscheiden die individuellen Bedürfnisse und Präferenzen der Mitarbeiter, weshalb ich eine Orientierung an Remote first (zwecks Dokumentation, asynchroner Kommunikation und Einbindung der Remotearbeiter) in Kombination mit einem Hybridmodell empfehlen würde.</p>
<p>Headerbild Attribution: Image by <a href="https://pixabay.com/users/thedarknut-2182155/?utm_source=link-attribution&utm_medium=referral&utm_campaign=image&utm_content=4938932">thedarknut</a> from <a href="https://pixabay.com/?utm_source=link-attribution&utm_medium=referral&utm_campaign=image&utm_content=4938932">Pixabay</a></p>
Sveltekit: Process dynamic data with svelte and sveltekit2021-06-29T16:41:38+00:001970-01-01T01:00:00+00:00https://bgrande.de/blog/sveltekit-process-dynamic-data-with-svelte-and-sveltekit/<p>Using sveltekit if you have used svelte before is quite straightforward. At least most of the time. <br />
Loading and displaying dynamic (as in by API call) wasn't that obvious to me at first. <br />
So here's a short description of the most important things to keep in mind.</p>
<span id="continue-reading"></span><br>
<h2 id="sveltekit-routes">Sveltekit Routes</h2>
<p>First of all, sveltekit allows you to use dynamic endpoints by just using the right filenames.</p>
<p>I.e. using <code>routes/test/[message].svelte</code> enables you to replace <code>[message]</code> with whatever parameter you want. Transform the parameters into props
<br><br></p>
<h3 id="transform-the-parameters-into-props">Transform the parameters into props</h3>
<p>By adding the following module to your <em>.svelte</em> file you kind of publish the parameters to your frontend.</p>
<pre data-lang="javascript" style="background-color:#fafafa;color:#61676c;" class="language-javascript "><code class="language-javascript" data-lang="javascript"><span style="color:#ed9366;"><</span><span>script context</span><span style="color:#ed9366;">=</span><span style="color:#86b300;">"module"</span><span style="color:#ed9366;">>
</span><span> </span><span style="color:#fa6e32;">export async function </span><span style="color:#f29718;">load</span><span>(</span><span style="color:#ff8f40;">context</span><span>) {
</span><span> </span><span style="color:#fa6e32;">let </span><span>year </span><span style="color:#ed9366;">= </span><span>context</span><span style="color:#ed9366;">.</span><span>page</span><span style="color:#ed9366;">.</span><span>params</span><span style="color:#ed9366;">.</span><span>message</span><span style="color:#61676ccc;">;
</span><span> </span><span style="color:#fa6e32;">return </span><span>{ props</span><span style="color:#61676ccc;">: </span><span>{ message }}
</span><span>}
</span><span style="color:#ed9366;"></</span><span>script</span><span style="color:#ed9366;">>
</span></code></pre>
<br>
<p>Also, you should add the following prop export to your svelte <code><script></code> notation:</p>
<pre data-lang="javascript" style="background-color:#fafafa;color:#61676c;" class="language-javascript "><code class="language-javascript" data-lang="javascript"><span style="color:#fa6e32;">export let </span><span>message</span><span style="color:#61676ccc;">;
</span></code></pre>
<br>
<p>Now you can use it within the markup part:</p>
<pre data-lang="html" style="background-color:#fafafa;color:#61676c;" class="language-html "><code class="language-html" data-lang="html"><span style="color:#55b4d490;"><</span><span style="color:#399ee6;">div</span><span style="color:#55b4d490;">></span><span>Your message: {message}</span><span style="color:#55b4d490;"></</span><span style="color:#399ee6;">div</span><span style="color:#55b4d490;">>
</span></code></pre>
<p><br><br></p>
<h2 id="handle-dynamic-data">Handle Dynamic data</h2>
<p>Another thing is handling dynamic content you got from an API call like this:</p>
<pre data-lang="javascript" style="background-color:#fafafa;color:#61676c;" class="language-javascript "><code class="language-javascript" data-lang="javascript"><span style="color:#fa6e32;">let </span><span>text </span><span style="color:#ed9366;">= </span><span style="color:#f29718;">getText</span><span>()</span><span style="color:#61676ccc;">;
</span><span style="color:#fa6e32;">async function </span><span style="color:#f29718;">getText</span><span>() {
</span><span> </span><span style="color:#fa6e32;">return await </span><span style="color:#f29718;">fetch</span><span>(</span><span style="color:#86b300;">'http://localhost:5000/api/text</span><span style="color:#f51818;">)
</span><span> </span><span style="color:#ed9366;">.</span><span style="color:#f07171;">then</span><span>(</span><span style="color:#ff8f40;">r </span><span style="color:#fa6e32;">=> </span><span>r</span><span style="color:#ed9366;">.</span><span style="color:#f29718;">json</span><span>())
</span><span> </span><span style="color:#ed9366;">.</span><span style="color:#f07171;">then</span><span>(</span><span style="color:#ff8f40;">data </span><span style="color:#fa6e32;">=> </span><span>{
</span><span> </span><span style="color:#fa6e32;">return </span><span>data</span><span style="color:#ed9366;">.</span><span>text</span><span style="color:#61676ccc;">;
</span><span> });
</span><span>}
</span></code></pre>
<br>
<p>This is similar to svelte's <em>onMount</em> function but that didn't work for me using the following async markup functions.</p>
<pre data-lang="html" style="background-color:#fafafa;color:#61676c;" class="language-html "><code class="language-html" data-lang="html"><span>{#await text}
</span><span> </span><span style="color:#55b4d490;"><</span><span style="color:#399ee6;">div</span><span style="color:#55b4d490;">></span><span>loading...</span><span style="color:#55b4d490;"></</span><span style="color:#399ee6;">div</span><span style="color:#55b4d490;">>
</span><span>{:then text}
</span><span> </span><span style="color:#55b4d490;"><</span><span style="color:#399ee6;">div</span><span style="color:#55b4d490;">></span><span>The text: {text}</span><span style="color:#55b4d490;"></</span><span style="color:#399ee6;">div</span><span style="color:#55b4d490;">>
</span><span>{:catch error}
</span><span> </span><span style="color:#55b4d490;"><</span><span style="color:#399ee6;">div</span><span style="color:#55b4d490;">><</span><span style="color:#399ee6;">p </span><span style="color:#f29718;">style</span><span style="color:#61676ccc;">=</span><span style="color:#86b300;">"</span><span style="color:#55b4d4;">color</span><span style="color:#61676ccc;">: </span><span style="font-style:italic;color:#ed9366;">red</span><span style="color:#86b300;">"</span><span style="color:#55b4d490;">></span><span>{error.message}</span><span style="color:#55b4d490;"></</span><span style="color:#399ee6;">p</span><span style="color:#55b4d490;">></</span><span style="color:#399ee6;">div</span><span style="color:#55b4d490;">>
</span><span>{/await}
</span></code></pre>
<br>
<p>Svelte is able to handle asynchronous content loading via the <code>{#await}</code> function very well.</p>
<p>Basically, this is like using the await syntax via javascript but within the markup.</p>
<p>So you don't have to care about the async logic and svelte will give you the output as soon as the request was completed.</p>
<p>It can even handle errors and is <a href="https://svelte.dev/tutorial/await-blocks" title="link to svelte await-blocks tutorial page">race condition save</a>.</p>
<p>That's it from me this month.</p>
Developing with a Curved Display2021-05-21T16:56:12+00:002021-05-22T15:37:35+00:00https://bgrande.de/blog/developing-with-a-curved-display/<p>When I searched for a new display with more space I wanted to know if using a curved for programming tasks could work. Turns out it is great!</p>
<span id="continue-reading"></span><br>
<h2 id="tl-dr">TL;DR</h2>
<p>As you might have guessed, this is my opinion and others might have different experiences.</p>
<p>That being said, I really enjoy working with a Curved 34" Monitor. For once, it's a lot of space and you can have your IDE and Browser side-by-side. Also, the curvature makes the large screen quite ergonomic. As in you can catch all the important details with having only one look at it.
<br><br></p>
<h2 id="why-a-curved-display">Why a Curved Display</h2>
<p>First of all, I kept searching for about a year or so for a new monitor. At first, I wanted either one or two 27" displays. Later on, the 34" display size seemed to be a good match for having less clutter on the desk (as in just only one monitor).
So I concentrated on 34-inch screens. Many of them are curved, nowadays. I googled a bit to find out how other software developers experienced the curvature. Turns out, the feelings were kind of mixed. Then I found the <a href="https://www.usa.philips.com/c-p/346B1C_27/curved-ultrawide-lcd-monitor-with-usb-c" title="external link to philips usa 346B1C monitor page">Philips 346B1C</a> for quite a low price at that time. Since the screen got a few extras like an integrated KVM and 90W USB-C charging it immediately caught my eye.
<br><br></p>
<h2 id="curvature">Curvature</h2>
<p>The curvature of the 346B1C is decent, but you'll still notice. At first, it felt a bit weird. But I got used to it quite fast. Nowadays, it feels weird working with non-curved Monitors...
The combination of having a lot of space on the screen and the curvature makes it perfect to work with. I can have two windows side-by-side and still don't have to move my head too much.
That being said, maybe, less screen size won't profit from the curvature that much but at 34" it certainly does.
<br><br></p>
<h2 id="web-software-development">Web Software Development</h2>
<p>The worst thing about buying this monitor was realizing that I should have done it even earlier. I really got more productive with that display and besides, it just looks nice as well.</p>
<p>Having this screen size as well as the curvature enabled me either reading docs and try it without changing windows as well as immediately being able to see the result when developing software.</p>
<p>The integrated KVM also enabled me to switch between two computers (one for work, the other for the other work 😀) quite fast.</p>
<p>So if you're a developer and undecided about a larger screen with or without curvature: just give it a try!</p>
Swoole: PHP with superpowers2021-04-13T16:29:16+00:002022-10-04T10:39:45+00:00https://bgrande.de/blog/php-with-superpowers/<p>You probably thought fast concurrency powers were only possible with NodeJs or maybe Go, even Rust? Well, think again! Here's how to do it with PHP</p>
<span id="continue-reading"></span><br>
<h2 id="look-mom-it-s-fast">Look mom it's fast!</h2>
<p>Roughly a year ago I had my first encounter with a relatively new PHP extension called <a href="https://www.swoole.co.uk/" title="external link to swoole homepage">Swoole</a>.</p>
<p>I was looking into Nodejs performance stories and was wondering how much the difference compared to current PHP implementations would be at the time.</p>
<p>PHP was making quite some progress but still, using the synchronous approach of PHP-FPM including I/O blocking lead to people <a href="https://blog.logrocket.com/getting-started-with-javascript-for-php-developers/" title="external link to article about switch from PHP to Nodejs">leaving for Nodejs</a> or other platforms.</p>
<p>After reading one <a href="https://www.toptal.com/back-end/server-side-io-performance-node-php-java-go" title="external link to article about server side io performance in php, java, go">interesting article</a>, I read some of the comments. Swoole was mentioned in one of them.</p>
<p>Besides, there was a <a href="https://gist.github.com/nkt/e49289321c744155484c" title="external link to github gist about performance comparison">long discussion on GitHub</a> about the performance including comparing it to Nodejs and other PHP implementations alike.</p>
<p>Also, Vesko Kenashkov did a lot of <a href="https://github.com/kenashkov/swoole-performance-tests" title="external link to github swoole performance review repository">different tests</a> comparing swoole to a similar Apache+PHP setup.</p>
<p>So I gave it a try. And, you already guessed it, I wasn't disappointed.</p>
<p>My simple test app outputting a list of randomly (per request) chosen strings won against most of the competitors, even Nodejs:</p>
<pre data-lang="javascript" style="background-color:#fafafa;color:#61676c;" class="language-javascript "><code class="language-javascript" data-lang="javascript"><span>use Swoole\Http\Request</span><span style="color:#61676ccc;">;
</span><span>use Swoole\Http\Response</span><span style="color:#61676ccc;">;
</span><span>use Swoole\Http\</span><span style="font-style:italic;color:#55b4d4;">Server</span><span style="color:#61676ccc;">;
</span><span>
</span><span>include_once __DIR__ </span><span style="color:#ed9366;">. </span><span style="color:#86b300;">'/vendor/autoload.php'</span><span style="color:#61676ccc;">;
</span><span>
</span><span>$server </span><span style="color:#ed9366;">= new </span><span style="color:#399ee6;">Server</span><span>(</span><span style="color:#86b300;">'0.0.0.0'</span><span style="color:#61676ccc;">, </span><span style="color:#ff8f40;">8080</span><span>)</span><span style="color:#61676ccc;">;
</span><span>
</span><span>$server</span><span style="color:#ed9366;">-></span><span style="color:#f29718;">set</span><span>([
</span><span> </span><span style="color:#86b300;">'log_level' </span><span style="color:#fa6e32;">=> </span><span style="color:#ff8f40;">3</span><span style="color:#61676ccc;">,
</span><span> </span><span style="color:#86b300;">'log_file' </span><span style="color:#fa6e32;">=> </span><span style="color:#86b300;">'/dev/stdout'</span><span style="color:#61676ccc;">,
</span><span> </span><span style="color:#86b300;">'enable_coroutine' </span><span style="color:#fa6e32;">=> </span><span style="color:#ff8f40;">true</span><span style="color:#61676ccc;">,
</span><span>])</span><span style="color:#61676ccc;">;
</span><span>
</span><span>$languageList </span><span style="color:#ed9366;">= </span><span>[</span><span style="color:#86b300;">'java'</span><span style="color:#61676ccc;">, </span><span style="color:#86b300;">'css'</span><span style="color:#61676ccc;">, </span><span style="color:#86b300;">'go'</span><span style="color:#61676ccc;">, </span><span style="color:#86b300;">'javascript'</span><span style="color:#61676ccc;">, </span><span style="color:#86b300;">'typescript'</span><span style="color:#61676ccc;">, </span><span style="color:#86b300;">'rust'</span><span style="color:#61676ccc;">, </span><span style="color:#86b300;">'php'</span><span style="color:#61676ccc;">, </span><span style="color:#86b300;">'swoole'</span><span style="color:#61676ccc;">, </span><span style="color:#86b300;">'html'</span><span style="color:#61676ccc;">, </span><span style="color:#86b300;">'sql'</span><span>]</span><span style="color:#61676ccc;">;
</span><span>
</span><span>$getLang </span><span style="color:#ed9366;">= </span><span>static </span><span style="color:#fa6e32;">function </span><span>(</span><span style="color:#ff8f40;">$list</span><span>) {
</span><span> $active </span><span style="color:#ed9366;">= </span><span>[]</span><span style="color:#61676ccc;">;
</span><span>
</span><span> </span><span style="color:#fa6e32;">for </span><span>($i </span><span style="color:#ed9366;">= </span><span style="color:#ff8f40;">0</span><span style="color:#61676ccc;">; </span><span>$i </span><span style="color:#ed9366;">< </span><span style="color:#ff8f40;">3</span><span style="color:#61676ccc;">; </span><span>$i</span><span style="color:#ed9366;">++</span><span>) {
</span><span> $max </span><span style="color:#ed9366;">= </span><span style="color:#f29718;">count</span><span>($list) </span><span style="color:#ed9366;">- </span><span style="color:#ff8f40;">1</span><span style="color:#61676ccc;">;
</span><span> $index </span><span style="color:#ed9366;">= </span><span style="color:#f29718;">floor</span><span>(</span><span style="color:#f29718;">random_int</span><span>(</span><span style="color:#ff8f40;">0</span><span style="color:#61676ccc;">, </span><span>$max))</span><span style="color:#61676ccc;">;
</span><span> </span><span style="color:#fa6e32;">if </span><span>(</span><span style="color:#ed9366;">!</span><span>\</span><span style="color:#f29718;">in_array</span><span>($list[$index]</span><span style="color:#61676ccc;">, </span><span>$active</span><span style="color:#61676ccc;">, </span><span style="color:#ff8f40;">true</span><span>)) {
</span><span> $active[] </span><span style="color:#ed9366;">= </span><span>$list[$index]</span><span style="color:#61676ccc;">;
</span><span> }
</span><span> }
</span><span>
</span><span> </span><span style="color:#fa6e32;">return </span><span>$active</span><span style="color:#61676ccc;">;
</span><span>}</span><span style="color:#61676ccc;">;
</span><span>
</span><span>$server</span><span style="color:#ed9366;">-></span><span style="color:#f29718;">on</span><span>(</span><span style="color:#86b300;">"request"</span><span style="color:#61676ccc;">, </span><span style="color:#fa6e32;">function </span><span>(</span><span style="color:#ff8f40;">Request $request</span><span style="color:#61676ccc;">, </span><span style="color:#ff8f40;">Response $response</span><span>) </span><span style="color:#f29718;">use </span><span>(</span><span style="color:#ff8f40;">$getLang</span><span style="color:#61676ccc;">, </span><span style="color:#ff8f40;">$languageList</span><span>) {
</span><span> $response</span><span style="color:#ed9366;">-></span><span style="color:#f29718;">header</span><span>(</span><span style="color:#86b300;">'Connection'</span><span style="color:#61676ccc;">, </span><span style="color:#86b300;">'close'</span><span>)</span><span style="color:#61676ccc;">;
</span><span> $response</span><span style="color:#ed9366;">-></span><span style="color:#f29718;">header</span><span>(</span><span style="color:#86b300;">'Content-Type'</span><span style="color:#61676ccc;">, </span><span style="color:#86b300;">'application/json'</span><span>)</span><span style="color:#61676ccc;">;
</span><span>
</span><span> $result </span><span style="color:#ed9366;">= </span><span>[
</span><span> </span><span style="color:#86b300;">'message' </span><span style="color:#fa6e32;">=> </span><span style="color:#86b300;">'The languages of the day are'</span><span style="color:#61676ccc;">,
</span><span> </span><span style="color:#86b300;">'list' </span><span style="color:#fa6e32;">=> </span><span style="color:#f29718;">$getLang</span><span>($languageList)
</span><span> ]</span><span style="color:#61676ccc;">;
</span><span>
</span><span> $response</span><span style="color:#ed9366;">-></span><span style="color:#f29718;">end</span><span>(</span><span style="color:#f29718;">json_encode</span><span>($result))</span><span style="color:#61676ccc;">;
</span><span>})</span><span style="color:#61676ccc;">;
</span><span>
</span><span>$server</span><span style="color:#ed9366;">-></span><span style="color:#f29718;">start</span><span>()</span><span style="color:#61676ccc;">;
</span></code></pre>
<br>
<p>What are we doing here? First, we create the HTTP swoole server via PHP object instantiation. We set some configuration vars for logging and making sure the coroutines are enabled.</p>
<p>Having done the configuration, we define our callback function (which could be some class, of course) for returning the randomized list with a maximum of three items which in this case is just a list of some programming languages.</p>
<p>This function will be run with an unsorted list as input on each request. The request callback sets some headers as well and returns the result via the swoole specific Response::end method.</p>
<p>You can find this <a href="https://github.com/bgrande/swoole-example" title="external link to github repository containing a working swoole example as described on this page">example code at Github</a> as well including a docker-compose based container configuration. You can easily test the app's performance via ab.
<br><br></p>
<h2 id="yes-but">Yes, but...</h2>
<p>Simply put, the Swoole developers adapted to the async non-blocking I/O concept Nodejs and Go were using as well.</p>
<p>However, by opting for the <a href="https://en.wikipedia.org/wiki/Coroutine" title="external link to wikipedia article about coroutines">Coroutine</a> (if you're interested in this, also read about the related concepts of <a href="https://en.wikipedia.org/wiki/Green_threads" title="external link to wikipedia article about green threads">green threads</a> and <a href="https://en.wikipedia.org/wiki/Fiber_(computer_science)" title="external link to wikipedia article about fibers">fibers</a>) concept, it's possible to keep the way of implementing your applications mostly like you've been used to in PHP. Without having to take care of too many callbacks or other async logic, as a plus.</p>
<p>And, on top of it, they implemented multiprocessor support. So no wonder we could see these high concurrency results.</p>
<p>After starting the Master Process the swoole instance will create several reactors (defaults to numbers of CPU and can be defined via config). The reactors will be used for handling the network connections and network I/O.</p>
<p>Also, there are worker processes that handle the data from reactor threads as well as executing the business logic. These workers then are, where the actual application code will be run with. <a href="https://www.swoole.co.uk/how-it-works" title="external link to swoole how it works article">Read More about how it works...</a></p>
<p>That's why swoole really turned out to be a worthwhile competitor to other platforms.</p>
<p>Therefore I wanted to give it a try and presented my findings to the team since we were planning on creating a new microservice API that perfectly fit the high concurrency requirements swoole seemed to solve.
<br><br></p>
<h2 id="how-to-use">How to use</h2>
<p>If you've developed Nodejs or Go based web applications and as you can see from the above example, the structure of swoole applications should be quite familiar.</p>
<p>You create and start a new server (i.e. a webserver) and listen for connections/requests using a callback.</p>
<p>Handling a WebSocket message might look like the following snippet:</p>
<pre data-lang="javascript" style="background-color:#fafafa;color:#61676c;" class="language-javascript "><code class="language-javascript" data-lang="javascript"><span>$server</span><span style="color:#ed9366;">-></span><span style="color:#f29718;">on</span><span>(</span><span style="color:#86b300;">'message'</span><span style="color:#61676ccc;">, </span><span style="color:#fa6e32;">function</span><span>(</span><span style="color:#ff8f40;">Server $server</span><span style="color:#61676ccc;">, </span><span style="color:#ff8f40;">Frame $frame</span><span>) {
</span><span> $server</span><span style="color:#ed9366;">-></span><span style="color:#f29718;">push</span><span>($frame</span><span style="color:#ed9366;">-></span><span>fd</span><span style="color:#61676ccc;">, </span><span style="color:#f29718;">json_encode</span><span>([</span><span style="color:#86b300;">"got ya message"</span><span style="color:#61676ccc;">, </span><span style="color:#f29718;">time</span><span>()]))</span><span style="color:#61676ccc;">;
</span><span>})</span><span style="color:#61676ccc;">;
</span></code></pre>
<p><br><br></p>
<h3 id="dev-things">Dev things</h3>
<p>If you want your editor to handle swoole as well, <a href="https://github.com/wudi/swoole-ide-helper" title="external link to github repository containing a swoole-ide-helper">look no further</a>. There also is a plugin for <a href="https://plugins.jetbrains.com/plugin/13040-swoole-ide-helper" title="external link to jetbrains swoole ide helper plugin">PHPStorm</a>.
<br><br></p>
<h3 id="making-it-even-faster">Making it even faster</h3>
<p>There's the concept called <a href="https://www.swoole.co.uk/docs/modules/swoole-table" title="external link to swoole tables documentation page">Swoole Tables</a>. It's an internal cache system (Key-Value Store) sharing all the cached data through all the processes and threads.</p>
<p>Therefore, once you have filled the table with data, each worker can access it at any request.
<br><br></p>
<h3 id="what-else-can-it-do">What else can it do</h3>
<p>Among even more possibilities we have the concept of Tasks, WebSockets or even UDP and TCP service implementations at our hands.</p>
<p>Therefore you can use PHP with swoole support for almost every network-based service you can think of. What about I/O?
<br><br></p>
<h3 id="what-about-external-datasources">What about external Datasources?</h3>
<p>Yes, this is handled as well. You can use database connections within coroutines which enables you to do more requests at once.</p>
<p>There are quite some clients for MySQL, Redis and Postgres reimplementing their existing APIs. The same goes for handling files.</p>
<p>Although, of course, the limiting factor still will be how many concurrent handles you can have and how long the data needs to process on the other end. So while you can improve certain I/O tasks here, common website logic like reading data from the database and sending it to the browser will only be slightly faster without caching.
<br><br></p>
<h2 id="the-downsides">The downsides</h2>
<p>In my opinion, the technical implementation is very good and the extension very stable. Apart from creating exceptions or using the API in the wrong way, I haven't encountered any issues so far.</p>
<p>There's one little downer, though: The documentation. While it covers the most important concepts and API usages it sometimes lacks details and useful examples.</p>
<p>In addition to that, you have to be quite careful (although, IMHO you always should be!) when writing your code since having these long-running processes will reveal any possible memory leaks which would otherwise not surface in a traditional stateless setup. So be aware of possible memory leaks and check for them regularly.</p>
<p>Also, just reloading after code changes won't work because the app code gets cached in memory when starting the application. So you will have to restart the PHP process.</p>
<p>Using Xdebug and/or Xhprof won't work, either. Although, regarding debugging, there are alternatives like <a href="https://github.com/swoole/yasd" title="external link to github repository of the yasd debugging project">yasd</a>.
<br><br></p>
<h2 id="the-book">The book</h2>
<p>To get a deeper understanding, I bought the <a href="https://swoolebook.com/" title="external link to the swoole book homepage">book</a>, though. I hoped for a bit more extensive explanations compared to the web documentation.</p>
<p>I'm currently still in the progress of reading it and so far I enjoyed reading it.</p>
<p>This is quite a new book about swoole. It has been written by <a href="https://twitter.com/doubaokun" title="external link to Bruce Dou's twitter account">Bruce Dou</a>, one of swoole's main developers.</p>
<p>He starts with describing the whys and how doing async I/O instead of the so far standard stateless PHP-FPM can be an improvement.</p>
<p>Then, he follows up by describing the concurrency model they chose for swoole and dives a bit into different concurrency concepts and their pros and cons.</p>
<p>After that, it's all about swoole, what it can be used for and how to use it.</p>
<p>Although the content is very good in helping to get an overview of the technologies and concepts used in developing swoole, the book, in my opinion, could have needed some proof-reading before publishing.</p>
<p>No offence! I'm not a native English speaker as well, so this might sound a bit harsh. But I really don't mean it that way since the book really helped me to get a deeper understanding so far.</p>
<p>That being said, there are a lot of syntactical errors which sometimes make sentences difficult to understand at first read.
<br><br></p>
<h2 id="alternatives">Alternatives</h2>
<p>While I think swoole's performance is outstanding there are quite a lot of competitors even in the PHP platform space which shouldn't be unmentioned.</p>
<p>To name a few: <a href="https://amphp.org/amp/" title="external link to the amp php project">amphp</a>, <a href="https://reactphp.org/" title="external link to the reactphp project">reactphp</a>, <a href="https://www.workerman.net/" title="external link to the php workerman project">workerman</a> and a partly <a href="http://socketo.me/" title="external link to the php ratchet project">ratchet</a>.</p>
<p>Also, as mentioned before, there are Go, Rust (I'm currently in the progress of learning that), Nodejs, Kotlin, Java and so on, which, depending on your preferences and problem at hand can be a good pick as well.</p>
<p>So, choose what you're familiar with and what seems to be best suited for your project. However, consider not just dropping PHP and giving PHP + Swoole a chance!</p>
<p>PHP devs don't sleep, either, and along with the language improvements in PHP 7 and higher, swoole adds a lot of features many applications are in need of, today.</p>
<p>Another thing, coming up with PHP 8.1 will be <a href="https://betterprogramming.pub/a-look-at-the-new-php-8-1-fibers-feature-979489399918" title="external link to article about php fibers">PHP fibers</a>, making another step into the async world.
<br><br></p>
<h2 id="who-s-using-it">Who's using it?</h2>
<p>Swoole is used by Chinese companies like Tencent (WeChat etc.).</p>
<p>Also, swoole is in use for the <a href="https://github.com/swooletw/laravel-swoole" title="external link to github repository of the laravel-swoole project">laravel</a> <a href="https://beyondco.de/blog/laravel-octane-introduction" title="external link to article about laravel octane">octane</a> project. For even more laravel related insights and swoole details, you might want to read <a href="https://divinglaravel.com/laravel-and-swoole" title="external link to article about laravel and swoole">this article by Mohamed Said</a>.</p>
<p>Obviously, especially in the laravel community, swoole seems to be used <a href="https://alkanyx.com/blog/post/27/php-on-steroids-swoole-introduction-and-benchmarks" title="external link to article about swoole in laravel context">quite often</a>.
<br><br></p>
<h2 id="frameworks">Frameworks</h2>
<p>There already exist some frameworks using swoole, making creating fast APIs and websites even easier.</p>
<p>Here's a non-conclusive list:</p>
<p>- <a href="https://crowphp.com/crowphp.com/" title="external link to the crowphp swoole framework project">Crow</a></p>
<p>- <a href="http://swoft.io/" title="external link to the swoft swoole framework project">Swoft</a></p>
<p>- <a href="https://github.com/easy-swoole/easyswoole" title="external link to the github repository of the easyswoole framework project">EasySwoole</a></p>
<p>- <a href="https://github.com/igniphp/framework" title="external link to the github repository of the igni swoole framework project">Igni</a>
<br><br></p>
<h2 id="conclusion">Conclusion</h2>
<p>Depending on your goals you really should consider swoole for your next application. Particularly when you'll be in need of high concurrency, swoole delivers.</p>
<p>So, if you're, let's say, going to write a microservice in need of handling a lot of requests at least give swoole a try.</p>
<p>That being said if you're just going to create another website or blog the old PHP-FPM based approach might really be enough. In fact, using just a static site generator might really be the best option for that purpose.</p>
<p>What do you think? Write to me on <a href="https://twitter.com/benediktgrande" title="external link to Benedikt's twitter account">Twitter</a>.
<br><br></p>
<h2 id="attribution">Attribution</h2>
<p>The image used as the article picture was based on <a href="http://www.freepik.com" title="external link to freepik image platform">Designed by macrovector / Freepik</a>.</p>
Image rotation made easy2021-04-06T11:10:43+00:002021-08-24T14:22:50+00:00https://bgrande.de/blog/image-rotation-made-easy/<p>As a computer enthusiast, I never thought rotating an image could be difficult. Turns out, for some people, it is. So why not change that?</p>
<span id="continue-reading"></span><br>
<h2 id="wait-another-image-rotation-tool">Wait, another image rotation tool?</h2>
<p>Well, kind of! When I was working as a freelance developer for a side-project the company had an issue using pictures that had a different rotation.</p>
<p>These were mostly supposed to be in portrait mode where the camera originally had been set to shoot in landscape and automatically rotate into portrait mode by <a href="https://en.wikipedia.org/wiki/Exif" title="EXIF description on wikipedia">EXIF</a>.</p>
<p>But after uploading these images to their content management application the EXIF information got stripped and the image turned out to be landscape instead of portrait mode as intended (and as it looked like on their PC).</p>
<p>So they called me and asked how to fix it. At first, I tried explaining the steps by using an Image Processing Software but that turned out to be a bit too complicated for an inexperienced user.</p>
<p>Then I had an idea: why not just write an app automatically rotating (as in applying the EXIF orientation information) your image(s) just by uploading it to a website and get the result immediately.
<br><br></p>
<h2 id="the-image-rotation-tool">The image rotation tool</h2>
<p>The logic behind that is quite simple. As long as the image (well, JPEG it has to be as well) contains EXIF orientation data you can just get the rotation angle and rotate the image as needed.</p>
<p>Even unflipping flipped images is not much of a deal this way.</p>
<p>Using either ImageMagick or gd2 via PHP extension here enables a lot of ways to deal with images.</p>
<p>The result is a <a href="https://rotate-image.com" title="Simple Image rotation webservice">simple website</a> serving as a web application frontend.</p>
<p>It accepts multiple JPEG images to be uploaded and tries rotating them as specified in the EXIF orientation information.</p>
<p>As soon as the rotation is done the image(s) will be returned and directly provided for downloading.</p>
<p>And that's basically it.
<br><br></p>
<h2 id="the-challenges">The challenges</h2>
<p>Wait, didn't you say the logic was simple? Indeed, and it really is.</p>
<p>But I'm still in the progress of becoming a modern web designer so it took me a while until I was sufficiently happy with what the paged looked like.</p>
<p>If you're interested, read more on parts of the design process and <a href="https://bgrande.de/blog/simple-on-scroll-animation-with-javascript-and-css.html" title="Blog post about how to rotate an image on page scroll via CSS and JS">how to rotate an image on scroll</a>.
<br><br></p>
<h2 id="are-people-using-it">Are people using it?</h2>
<p>Well, so far not many. I'm not even sure if there will ever be enough people in need for this.</p>
<p>But it was a fun experience and I learned a few things. My goals were:</p>
<p>1. Stay as minimal as possible (aka MVP)</p>
<p>2. Get to production as fast as possible</p>
<p>3. Just do one thing and do it as easy as possible</p>
<p>4. Really launch it</p>
<p>5. Gather feedback</p>
<p>6. Write up the learnings</p>
<p>7. Score at least 95 at google lighthouse for every check but PWA</p>
<p>So far I managed to achieve: #1, #3, #4, #7. Why not #2 you might ask! Well, the application was done pretty fast but as I said, the design...</p>
<p>What about #6 and #5? Well, that's what this article is about, among sharing the application to other people.
<br><br></p>
<h2 id="what-i-have-learned">What I have learned</h2>
<p>1. Even if you want to stay lean with your code try using an existing UI framework to accelerate going live and looking good (will do that next time)</p>
<p>2. Include at least one unusual design element (like having an image rotated on scroll)</p>
<p>3. Just restricting you to one thing really helps to get something to launch</p>
<p>4. There were a lot of interruptions where I couldn't work on this project. But never give up and stick with it. Even if it won't be used that much, training your ability to stick with something is worth a lot. Satisfying google lighthouse and pagespeed insights
<br><br></p>
<h3 id="satisfying-google-lighthouse-and-pagespeed-insights">Satisfying google lighthouse and pagespeed insights</h3>
<p>I call myself proud having achieved almost a full 100% for every category of <a href="https://developers.google.com/speed/pagespeed/insights/" title="google pagespeed requirements and description">google's pagespeed</a> requirements, especially all the <a href="https://support.google.com/webmasters/answer/9205520?hl=en" title="google core web vitals description">Core Web Vitals</a>.</p>
<p>See for yourself on the following images:</p>
<p>Mobile</p>
<p><img src="https://bgrande.de/blog/image-rotation-made-easy/lighthouse-results-mobile-for-rotate-image.com.png" alt="lighthouse results mobile for rotate-image.com" /></p>
<p>Desktop</p>
<p><img src="https://bgrande.de/blog/image-rotation-made-easy/lighthouse-results-desktop-for-rotate-image.com.png" alt="lighthouse results desktop for rotate-image.com" />
<br><br></p>
<h2 id="possible-future-features">Possible future features</h2>
<p>Being able to rotate an image automatically, even when there was no EXIF information, would be a great extension so rotating images could become even easier.</p>
<p>This, though, is a bit more of a challenge and requires training some Machine Learning Models to correctly recognize rotated images and how to fix the rotation.</p>
<p>I already thought a bit about that challenge and did some minor tests. Also, this could be a way of monetizing the service.</p>
<p>Although, I would probably need a lot of sample images and processing power to pull this off with a high enough precision.</p>
<p>What do you think?</p>
<p>If you want to get in touch, try writing to me <a href="https://twitter.com/benediktgrande" title="Benedikt's twitter account">on twitter</a>.</p>
Sharing more and writing more often2021-04-02T20:46:32+00:002021-04-02T20:58:46+00:00https://bgrande.de/blog/sharing-more-and-writing-more-often/<p>For the last few years I didn't write much on this blog. Read how and why I'm going to change this now.</p>
<span id="continue-reading"></span><br>
<h2 id="reasons-for-not-writing">Reasons for not writing</h2>
<p>Well, there are obvious reasons, like laziness, I guess. And a few more in my case.
<br><br></p>
<h3 id="lack-of-topics">Lack of topics</h3>
<p>Then, I always thought, for most of the topics I wanted to write about, somebody else already took care of. While this was probably true, there always is another angle you could look at things or another insight I might have. So why not start sharing something?
<br><br></p>
<h3 id="lack-of-time">Lack of time</h3>
<p>Also, I just didn't have enough time. Or so I thought. Actually, just getting started and write something instead of waiting until I would have time to write a full-blown article makes you have enough time for almost everything in the end. Even more in-depth, detailled stories are possible. It might only take a bit longer.
<br><br></p>
<h3 id="fear-of-criticism">Fear of criticism</h3>
<p>Amongst time and thinking I didn't have something to write about I was also afraid of criticism, I guess. Probably, I still am. But I accepted that it is part of the knowledge sharing thing. And an important part of learning. So, yeah!
<br><br></p>
<h2 id="the-goals">The Goals</h2>
<p>So I set some goals to myself:</p>
<p>1. Publish at least one article per month.</p>
<p>2. Work on at least one article per day for around 10-15 minutes.</p>
<p>3. Build up a pipeline of roughly aligned topics to write on to make #1 and #2 easier to succeed.</p>
<p>These goals will eventually enable me to get more used to writing, publishing and sharing knowledge.</p>
<p>And it might help me as well finding knowledge gaps.</p>
<p>What do you think? Write me and on <a href="https://twitter.com/benediktgrande" title="Write me in twitter">twitter</a> or via <a href="mailto:dev@bgrande" title="Mail me"></a><a href="mailto:dev@bgrande.de" title="Mail me">E-Mail</a>.</p>
Simple on scroll animation with JavaScript and CSS2021-03-05T10:59:57+00:002022-09-29T12:23:43+00:00https://bgrande.de/blog/simple-on-scroll-animation-with-javascript-and-css/<p>Read how I created a simple animation for my side-project to rotate an image on scrolling the page by using JavaScript and CSS.</p>
<span id="continue-reading"></span><br>
<h2 id="the-project">The project</h2>
<p>For my side-project idea <a href="https://rotate-image.com/" title="Link to external website about automatically rotating an uploaded image">rotate image</a>,
I wanted to do include a more unusual effect that would fit the application's purpose.</p>
<p>Since it was about automatically rotating images, rotating an image on scrolling the website seemed to be a perfect fit.</p>
<p>If you're interested in the project you can read more at <a href="https://bgrande.de/blog/image-rotation-made-easy/" title="Link to another blog article about creating the rotate image project">image rotation made easy</a>.
<br><br></p>
<h2 id="how-to-rotate-an-image-on-scroll">How to rotate an image on scroll</h2>
<p>Actually, it's not that much magic involved here.
You need some JavaScript and CSS code and some thinking about when, how far and at which speed the image should rotate when you scroll on the page.</p>
<p>So let's start straight with the markup:</p>
<pre data-lang="html" style="background-color:#fafafa;color:#61676c;" class="language-html "><code class="language-html" data-lang="html"><span style="color:#55b4d490;"><</span><span style="color:#399ee6;">div </span><span style="color:#f29718;">class</span><span style="color:#61676ccc;">=</span><span style="color:#86b300;">"container"</span><span style="color:#55b4d490;">>
</span><span> </span><span style="color:#55b4d490;"><</span><span style="color:#399ee6;">div </span><span style="color:#f29718;">class</span><span style="color:#61676ccc;">=</span><span style="color:#86b300;">"image-container"</span><span style="color:#55b4d490;">>
</span><span> </span><span style="color:#55b4d490;"><</span><span style="color:#399ee6;">img </span><span style="color:#f29718;">src</span><span style="color:#61676ccc;">=</span><span style="color:#86b300;">"rotated-image.png" </span><span style="color:#f29718;">alt</span><span style="color:#61676ccc;">=</span><span style="color:#86b300;">"the image to be rotated on scroll" </span><span style="color:#f29718;">width</span><span style="color:#61676ccc;">=</span><span style="color:#86b300;">"765" </span><span style="color:#f29718;">height</span><span style="color:#61676ccc;">=</span><span style="color:#86b300;">"1080" </span><span style="color:#55b4d490;">/>
</span><span> </span><span style="color:#55b4d490;"></</span><span style="color:#399ee6;">div</span><span style="color:#55b4d490;">>
</span><span style="color:#55b4d490;"></</span><span style="color:#399ee6;">div</span><span style="color:#55b4d490;">>
</span></code></pre>
<br>
<p>It could be done using a CSS background-image as well, it doesn't really matter. The most important element here is <code>image-container</code>.</p>
<p>Right now there would only be an image all over the page.
For presentation and clarity, we should increase the canvas height and add some margin at the top, so we can see the scrolling.</p>
<p>The CSS code should look like this:</p>
<pre data-lang="css" style="background-color:#fafafa;color:#61676c;" class="language-css "><code class="language-css" data-lang="css"><span style="color:#f29718;">.container </span><span>{
</span><span> </span><span style="color:#55b4d4;">width</span><span style="color:#61676ccc;">: </span><span style="color:#ff8f40;">100</span><span style="color:#fa6e32;">vw</span><span style="color:#61676ccc;">;
</span><span> </span><span style="color:#55b4d4;">height</span><span style="color:#61676ccc;">: </span><span style="color:#ff8f40;">200</span><span style="color:#fa6e32;">vh</span><span style="color:#61676ccc;">;
</span><span> </span><span style="color:#55b4d4;">margin</span><span style="color:#61676ccc;">: </span><span style="color:#ff8f40;">3</span><span style="color:#fa6e32;">rem </span><span style="color:#ff8f40;">0</span><span style="color:#61676ccc;">;
</span><span> </span><span style="color:#55b4d4;">position</span><span style="color:#61676ccc;">: </span><span style="font-style:italic;color:#ed9366;">relative</span><span style="color:#61676ccc;">;
</span><span>}
</span><span>
</span><span style="color:#f29718;">.image-container </span><span>{
</span><span> </span><span style="color:#55b4d4;">width</span><span style="color:#61676ccc;">: </span><span style="color:#ff8f40;">765</span><span style="color:#fa6e32;">px</span><span style="color:#61676ccc;">;
</span><span> </span><span style="color:#55b4d4;">height</span><span style="color:#61676ccc;">: </span><span style="font-style:italic;color:#ed9366;">auto</span><span style="color:#61676ccc;">;
</span><span> </span><span style="color:#55b4d4;">position</span><span style="color:#61676ccc;">: </span><span style="font-style:italic;color:#ed9366;">relative</span><span style="color:#61676ccc;">;
</span><span> </span><span style="color:#55b4d4;">display</span><span style="color:#61676ccc;">: </span><span style="font-style:italic;color:#ed9366;">block</span><span style="color:#61676ccc;">;
</span><span> </span><span style="color:#55b4d4;">margin</span><span style="color:#61676ccc;">: </span><span style="color:#ff8f40;">0 </span><span style="font-style:italic;color:#ed9366;">auto</span><span style="color:#61676ccc;">;
</span><span> </span><span style="color:#55b4d4;">transform</span><span style="color:#61676ccc;">: </span><span style="font-style:italic;color:#ed9366;">none</span><span style="color:#61676ccc;">;
</span><span>}
</span></code></pre>
<br>
<p>We set the container to a height of <code>200vh</code> so we can scroll on that page! Also, we add a margin-top of <code>3rem</code> so we can still see the image when scrolling on the page.
Therefore, the image container needs a width less than the canvas, which <code>765px</code> should match in most cases.
Additionally, <code>margin-right</code> and <code>margin-left</code> of the image container are set to <code>auto</code>. This enables us to center the canvas.
Lastly, we set the <code>transform</code> to <code>none</code>, here.
That's because we any transformation (or animation) to be disabled at the beginning.</p>
<p>And that's already most of it for CSS. Now the JavaScript code:</p>
<pre data-lang="javascript" style="background-color:#fafafa;color:#61676c;" class="language-javascript "><code class="language-javascript" data-lang="javascript"><span style="color:#fa6e32;">const </span><span>minTransform </span><span style="color:#ed9366;">= </span><span style="color:#ff8f40;">0</span><span style="color:#61676ccc;">;
</span><span style="color:#fa6e32;">const </span><span>maxTransform </span><span style="color:#ed9366;">= </span><span style="color:#ff8f40;">90</span><span style="color:#61676ccc;">;
</span><span style="color:#fa6e32;">const </span><span>ScrollThreshold </span><span style="color:#ed9366;">= </span><span style="color:#ff8f40;">5</span><span style="color:#61676ccc;">;
</span><span>
</span><span style="color:#fa6e32;">let </span><span>transform </span><span style="color:#ed9366;">= </span><span>minTransform</span><span style="color:#61676ccc;">;
</span></code></pre>
<br>
<p>First, we define our base variables:</p>
<ol>
<li>we want a minimal transformation threshold: <code>minTransform = 0</code></li>
<li>and a max threshold: <code>maxTransform = 90</code></li>
<li>the next threshold is at which scroll position we want to start the rotation: <code>scrollThreshold = 5</code></li>
</ol>
<p>Last but not least the transformation value should start with the minimal transformation.</p>
<p>And now the rotation function in JavaScript code:</p>
<pre data-lang="javascript" style="background-color:#fafafa;color:#61676c;" class="language-javascript "><code class="language-javascript" data-lang="javascript"><span style="color:#fa6e32;">function </span><span style="color:#f29718;">rotate</span><span>(</span><span style="color:#ff8f40;">transform</span><span>) {
</span><span> </span><span style="color:#fa6e32;">const </span><span>image </span><span style="color:#ed9366;">= </span><span>document</span><span style="color:#ed9366;">.</span><span style="color:#f07171;">querySelector</span><span>(</span><span style="color:#86b300;">'.image-container'</span><span>)</span><span style="color:#61676ccc;">;
</span><span> </span><span style="color:#fa6e32;">let </span><span>transformString </span><span style="color:#ed9366;">= </span><span style="color:#86b300;">'rotate(' </span><span style="color:#ed9366;">+ </span><span>transform </span><span style="color:#ed9366;">+ </span><span style="color:#86b300;">'deg)'</span><span style="color:#61676ccc;">;
</span><span>
</span><span> </span><span style="color:#fa6e32;">if </span><span>(image</span><span style="color:#ed9366;">.</span><span>style</span><span style="color:#ed9366;">.</span><span>transform </span><span style="color:#ed9366;">!== </span><span>transformString) {
</span><span> image</span><span style="color:#ed9366;">.</span><span>style</span><span style="color:#ed9366;">.</span><span>transform </span><span style="color:#ed9366;">= </span><span>transformString</span><span style="color:#61676ccc;">;
</span><span> }
</span><span>}
</span></code></pre>
<br>
<p>The main thing we're doing here is getting the image-container and setting the rotation value based on the transformation angle we get as a parameter.
As long as it's not the same value, we update the CSS transform value with the <code>rotate</code> transformation.</p>
<p>We still need to track the scroll position and determine the rotation angle by that:</p>
<pre data-lang="javascript" style="background-color:#fafafa;color:#61676c;" class="language-javascript "><code class="language-javascript" data-lang="javascript"><span style="color:#fa6e32;">function </span><span style="color:#f29718;">trackScrollPosition</span><span>(</span><span style="color:#ff8f40;">e</span><span>) {
</span><span> e</span><span style="color:#ed9366;">.</span><span style="color:#f07171;">preventDefault</span><span>()</span><span style="color:#61676ccc;">;
</span><span>
</span><span> </span><span style="color:#fa6e32;">const </span><span>step </span><span style="color:#ed9366;">= </span><span>e</span><span style="color:#ed9366;">.</span><span>target</span><span style="color:#ed9366;">.</span><span>scrollingElement</span><span style="color:#ed9366;">.</span><span>scrollTop</span><span style="color:#61676ccc;">;
</span><span> </span><span style="color:#fa6e32;">const </span><span>y </span><span style="color:#ed9366;">= </span><span>window</span><span style="color:#ed9366;">.</span><span>scrollY</span><span style="color:#61676ccc;">;
</span><span>
</span><span> </span><span style="color:#fa6e32;">if </span><span>(step </span><span style="color:#ed9366;"><= </span><span>startThreshold) {
</span><span> </span><span style="color:#f29718;">rotate</span><span>(</span><span style="color:#ff8f40;">0</span><span>)</span><span style="color:#61676ccc;">;
</span><span> </span><span style="color:#fa6e32;">return</span><span style="color:#61676ccc;">;
</span><span> }
</span><span>
</span><span> transform </span><span style="color:#ed9366;">= </span><span>(step </span><span style="color:#ed9366;">- </span><span>startThreshold)</span><span style="color:#61676ccc;">;
</span><span>
</span><span> </span><span style="color:#fa6e32;">if </span><span>(transform </span><span style="color:#ed9366;">>= </span><span>maxTransform) {
</span><span> transform </span><span style="color:#ed9366;">= </span><span>maxTransform</span><span style="color:#61676ccc;">;
</span><span> } </span><span style="color:#fa6e32;">else if </span><span>(transform </span><span style="color:#ed9366;"><= </span><span>startThreshold) {
</span><span> transform </span><span style="color:#ed9366;">= </span><span style="color:#ff8f40;">0</span><span style="color:#61676ccc;">;
</span><span> }
</span><span>
</span><span> </span><span style="color:#f29718;">rotate</span><span>(transform)</span><span style="color:#61676ccc;">;
</span><span>}
</span></code></pre>
<br>
<p>Long story short, we first get the current vertical scrolling value and check if that's already above our threshold where we wanted to start the rotation.</p>
<p>If yes, we go on and compute the current angle the image should be rotated to.
While doing this we're making sure not to get above or below our max and min thresholds.
For different kind of scrolling animations you might want to allow i.e. going in circles. </p>
<p>As soon as we have computed our rotation angle we call the rotate function from above.</p>
<p>Almost done now. We just need to get the scroll tracking hooked up somewhere.
For that, we're adding a listener for the scroll event binding our function from above:</p>
<pre data-lang="javascript" style="background-color:#fafafa;color:#61676c;" class="language-javascript "><code class="language-javascript" data-lang="javascript"><span>document</span><span style="color:#ed9366;">.</span><span style="color:#f07171;">addEventListener</span><span>(</span><span style="color:#86b300;">"DOMContentLoaded"</span><span style="color:#61676ccc;">, </span><span style="color:#fa6e32;">function</span><span>(</span><span style="color:#ff8f40;">event</span><span>) {
</span><span> document</span><span style="color:#ed9366;">.</span><span style="color:#f07171;">addEventListener</span><span>(</span><span style="color:#86b300;">'scroll'</span><span style="color:#61676ccc;">, </span><span>trackScrollPosition</span><span style="color:#61676ccc;">, </span><span style="color:#ff8f40;">false</span><span>)</span><span style="color:#61676ccc;">;
</span><span>})</span><span style="color:#61676ccc;">;
</span></code></pre>
<br>
<p>That's it. We have an image that rotates 90 degrees when scrolling the page.
Now you can try different variations regarding the rotation or scroll positions. </p>
<p>Have fun!</p>
<p>If you want to see the full code of the scrolling example, try <a href="https://github.com/bgrande/page-scroll-rotation-example" title="github repository containing sample code">https://github.com/bgrande/page-scroll-rotation-example</a>.</p>
Useful Remote Setup Equipment2021-01-18T12:50:24+00:002021-01-18T12:53:56+00:00https://bgrande.de/blog/useful-remote-setup-equipment/<p>Working remotely causes some challenges, especially on a hardware level. To increase the quality of video calls I levelled up my computer setup. Here are some of the equipment I bought.</p>
<span id="continue-reading"></span><br>
<h2 id="disclaimer">Disclaimer</h2>
<p>The following list of items is in no way conclusive or the right fit for everybody. Also, it's not the most sophisticated setup you can get. For example, using a DSLR or DSLM combined with a high-quality HDMI video converter would improve the video quality by a lot.</p>
<p>I took a few shortcuts to get where I wanted to get and only bought stuff I was sure I could need and what seemed appropriate for the intended usage regarding the price.</p>
<p>Although there are links to Amazon and other e-commerce pages I do not have any affiliate relation or otherwise to these suppliers.
<br><br></p>
<h2 id="the-items">The Items</h2>
<p>What did I need?</p>
<ol>
<li>A better microphone</li>
<li>A better camera</li>
<li>Some lighting options</li>
<li>Monitor mount</li>
<li>Mic mount</li>
<li>Few more minor things
<br><br></li>
</ol>
<h3 id="microphone">Microphone</h3>
<p>First, in my experience sound matters. There are a ton of good Mics out there so I will name a few:</p>
<p>Undoubtedly and proven by many YouTubers, podcasters and so on the <a href="https://www.bluemic.com/de-de/products/yeti/" title="Blue Yeti Microphone">Blue Yeti</a> is one hell of a mic. Here's the catch: If you're not going into being pro it's probably a bit too much and definitely not on a budget.</p>
<p>So I opted for the <a href="https://www.thomann.de/de/the_t.bone_sc_420_usb_desktop_set.htm" title="Thomann t.bone SC 420 USB Microphone">thomann .tbone SC 420</a>. It's half the price, fewer features but still of good quality.
<br><br></p>
<h3 id="camera">Camera</h3>
<p>We're talking about a pandemic so the <a href="https://www.logitech.com/de-de/product/hd-pro-webcam-c920" title="Logitech C920 Webcam">high-end webcams</a> were (not so much anymore) just totally overpriced.</p>
<p>So I went with the <a href="https://www.amazon.de/AUKEY-Mikrofon-Aufnahme-Kompatibel-Windows/dp/B0721MKXQ2" title="Aukey Webcam 1080p">Aukey webcam</a> you could get at Amazon for a fair price.</p>
<p>Although the Logitech C920 (and others) definitely is better, the Aukey webcam still improved the video quality by a lot compared to the Laptop's built-in camera.
<br><br></p>
<h3 id="lighting">Lighting</h3>
<p>To further improve the lighting and create higher quality camera takes I also bought some <a href="https://www.amazon.de/Neewer-USB-LED-Videolicht-Tisch-Flachwinkelaufnahmen-Produktportr%C3%A4t-YouTube-Videofotografie/dp/B07T8FBZC2" title="Neweer USB LED videolight">lighting equipment</a> when it was available with a special offer.</p>
<p>It's good for video lighting when it's dark although for the webcam the monitor light seems mostly sufficient.
<br><br></p>
<h3 id="monitor-mount">Monitor mount</h3>
<p>To have a space-efficient desk I also got some <a href="https://www.amazon.de/gp/product/B07DWM9WNM" title="Monitor and Laptop desk mount up to 10kg">Monitor and Laptop 2 in 1 desk mount</a>.</p>
<p>It can hold as much as 10kg so even my 34" Monitor could be used with it.</p>
<p>Besides creating more space on the desk and looking a bit more stylish this enabled me to also add a mic mount in the mount setup for more efficiency.
<br><br></p>
<h3 id="mic-mount">Mic Mount</h3>
<p>There are tons of mic mounts out there so I just picked one that matched my needs:</p>
<ol>
<li>being flexible</li>
<li>having a shock mount</li>
<li>being compatible with the existing mic</li>
</ol>
<p><a href="https://www.amazon.de/gp/product/B073VJKD9Q" title="Mic desk mount compatible with Blue Yeti and Thomann t.bone">So here we go</a>.
<br><br></p>
<h3 id="few-minor-things">Few minor things</h3>
<p>For adding the lights to the desk: a <a href="https://www.amazon.de/gp/product/B000NU2V7W" title="Desk mount only adapter">desk mount only adapter</a> and some <a href="https://www.amazon.de/gp/product/B07LFS2DQL" title="Converter and adapter screws">converters/adapters</a>.</p>
12 things you should know when working from home2020-05-14T13:36:14+00:002022-10-06T12:37:08+00:00https://bgrande.de/blog/12-things-you-should-know-when-working-from-home/<p>Ok first of all this is my first article with using a clickbait title. I wanted to see for myself what difference it made. <br />
And now back to the topic: Since I have been working remotely for about 2 years now I wanted to share some of my experiences. As well as things that I could have done better when starting and things that went well from the beginning. </p>
<span id="continue-reading"></span><br>
<h2 id="why-do-i-work-remotely">Why do I work remotely?</h2>
<p>It came out of necessity. Having a child with severe sleep issues we needed more family help. So we wanted to move near parts of our family.</p>
<p>Changing the employer was out of the question since I really liked it there and working remotely with the team seemed possible.</p>
<p>After many discussions, we came to an agreement, and we moved.</p>
<p>Luckily I already had some experience having a remote work setup. During my university time, I already enjoyed doing my learnings at home. As well as doing some freelance work from home.</p>
<p>Therefore, I really knew what I was up to and if I would be able to keep myself motivated.</p>
<p>Also, the COVID-19 crisis didn't bring much of a surprise for my way of working. The only (well, that's a big thing, actually) additional thing to take care of was the now-missing day-care for our first-born.
<br><br></p>
<h2 id="here-s-what-you-should-know">Here's what you should know</h2>
<p>At first here is the list of what I think are the most positive things about working remotely:</p>
<ol>
<li><strong>No commute</strong> - It really saves you a lot of time you can have with your family (and for side-projects).</li>
<li><strong>More freedom</strong> on how to structure your day - Apart from having meetings you could even work at night if that suits you.</li>
<li>Having <strong>more time for family</strong> - related to the missing commute, but not only.</li>
<li>Being <strong>more productive</strong> - That's only true if you can keep distractions to a minimum, though.</li>
</ol>
<p>There are downsides as well:</p>
<ol>
<li>It will be more <strong>difficult connecting</strong> to the team, especially if it's a partly remote team</li>
</ol>
<p>And now the things the topic promised:
<br><br></p>
<h3 id="1-be-prepared">1. Be prepared</h3>
<p>You should prepare yourself for working at home. <br>
Where do you want or are you able to do your work?
Can you get a quiet spot somewhere?
When will you do the work? What do you need to work effectively?
How are you going to get food and when?
What's about your family? Your spouse?
Since I was already used doing work from home I already knew the answers to most of these questions.
Sometimes you just have to try, though.
<br><br></p>
<h3 id="2-get-a-room">2. Get a room</h3>
<p>If it's in any way possible you should create a workplace in a separate room. If that's not possible you can also use the bedroom, for example.
As long as you can manage to get some quiet time and some space for a desk in this room, everything is ok.
Well, if using the bedroom, you should be able to mentally separate "working time" and "free time", too!
<br><br></p>
<h3 id="3-get-a-standing-desk">3. Get a standing desk</h3>
<p>I know this might be an issue, especially if you're short on space or do not want or are not allowed to work from home permanently.
There are <a href="https://www.amazon.com/Adjustable-Standing-Converter-Sit-Stand-Workstation/dp/B07TN47GV8" title="standing desk cover link to amazon">desk covers</a> enhancing your desk to be converted into a standing desk.
But even sth. small like a <a href="https://www.amazon.com/Techni-Mobili-RTA-B002-GPH06-Adjustable-Graphite/dp/B003M96GY0" title="portable laptop standing desk link to amazon">portable laptop desk</a> might help.
Many things you can build on your own via DYI as well. Now, I hear you saying "But why would I need that?". It helps you to change your body's position.
Instead of sitting down all the time get up with your desk and stand.
Your back will thank you in the end!
<br><br></p>
<h3 id="4-make-a-daily-schedule">4. Make a daily schedule</h3>
<p>While when working from home with children this might not always be actionable, at least it helps you to have some orientation on what you want to do and when, today.
So in the evening make a plan for the next day about what and (more or less) when you want and have to do the upcoming day.
Don't forget to plan time for relaxation as well as having food.
Many people forget eating and taking breaks when starting to work remotely.
<br><br></p>
<h3 id="5-reduce-the-pressure">5. Reduce the pressure</h3>
<p>If you're anything like me, you might put much of a pressure on yourself getting things done fast and efficiently.
While being more effective often can be true for working remotely there are no guarantees.
Either working from home just isn't your thing or there are things you can't control.
Like kids at home, and you have to take care of them somehow, while doing your work.
Or just not having the right equipment or space.
In such a case, take a step back and be happy with what you are able to do without thinking so much about what you could have achieved.
<br><br></p>
<h3 id="6-use-timebox-techniques-like-pomodoro">6. Use timebox techniques like Pomodoro</h3>
<p>Well, <a href="https://en.wikipedia.org/wiki/Pomodoro_Technique" title="Link to the wikipedia article about the pomodoro technique">working time-boxed</a> is not only useful for working at home, but it helps with a few things:</p>
<ol>
<li>Getting into "work mood"</li>
<li>Keep concentrated on one task even though there might be distractions</li>
<li>Keeping track of what you did for how long</li>
<li>Preventing you from getting sucked into a topic too deeply. So you can realize if you might be wasting time more early.</li>
</ol>
<p>Using Pomodoro on a regular basis was something I started after quite some time, and it definitely helped a lot.
<br><br></p>
<h3 id="7-avoid-meetings-if-possible">7. Avoid meetings if possible</h3>
<p>Having remote meetings can be even more stressful than being at an on-site meeting. In my opinion, there are three reasons for this:</p>
<ol>
<li>You have to concentrate more: on the screen and trying to get the people's body language via mostly seeing their faces.</li>
<li>Remote meetings often take longer than necessary. This might become getter over time, though. And it helps to stick to an agenda.</li>
<li>You can easily get distracted on your PC if you have other communication channels open.</li>
</ol>
<p>Actually I'm always tempted to skip almost any meeting... Of course, that's not possible and not good, either.
<br><br></p>
<h3 id="8-get-an-external-monitor-keyboard-and-mouse">8. Get an external monitor, keyboard and mouse</h3>
<p>If you're working via Laptop (with a desktop anyway) you should get another monitor (24" ore higher), <a href="https://www.microsoft.com/accessories/products/keyboards/natural-ergonomic-keyboard-4000/b2m-00001" title="Microsoft ergonomic keyboard 4000">keyboard</a> and <a href="https://www.amazon.com/Anker-Wireless-Vertical-Ergonomic-Optical/dp/B00BIFNTMC" title="ergonomic wireless mouse link to amazon">mouse</a>. I prefer having something ergonomic.
<br><br></p>
<h3 id="9-keep-track-of-your-work">9. Keep track of your work</h3>
<p>This should include keeping track of what you did and, and IMHO that's important, of what you have learned.</p>
<p>Out of two reasons, keep track of your work:</p>
<ol>
<li>For yourself to see what you did and what you learned each day</li>
<li>For your employer, so he can see what you did (and you got something to prove if there should ever be any doubt)</li>
</ol>
<p>This is something I adopted to quite recently.
<br><br></p>
<h3 id="10-don-t-forget-to-exercise">10. Don't forget to exercise</h3>
<p>Since commuting often implies getting some exercise because you're moving you will have to replace that activity somehow. So having breaks to do some smaller exercises as well as getting out and do active walking or running should be part of your daily schedule.</p>
<p>Having little children and not enough sleep most of the time it proved quite difficult to do a real workout on a regular basis. But I managed to get a great walk every day, at least.
<br><br></p>
<h3 id="11-separate-between-working-time-and-living-time">11. Separate between "working time" and "living time"</h3>
<p>As mentioned earlier you should learn to separate the time you are doing work from the time you're spending with your family or for yourself.
Most of the time you're probably doing great, so you don't have to always think about work.
Or even do longer hours because you seem to have the time now, or it seems to be appropriate.
It really is important to have time for yourself and getting some rest! While I don't think you always have to have the same schedule or should never do work if you have, i.e. an idea, you should at least ask yourself if it really is necessary. And don't let work "eat you up"!
<br><br></p>
<h3 id="12-think-about-prospects">12. Think about prospects</h3>
<p>Working remote does not necessarily mean working from home. So you could (at post-corona times) try working from a café, co-working spaces or even when travelling.
<br><br></p>
<h2 id="tools">Tools</h2>
<p>If you're looking for tools you can use for remote work here's a shortlist of some sources and tools:</p>
<ul>
<li><a href="https://tasklog.app/">https://tasklog.app/</a></li>
<li><a href="https://www.remote.tools/">https://www.remote.tools/</a></li>
<li><a href="https://basecamp.com/">https://basecamp.com/</a></li>
</ul>
Über den guten und nicht so guten Umgang mit Passwörtern2017-07-01T16:01:11+00:002019-09-18T16:56:29+00:00https://bgrande.de/blog/uber-den-guten-und-nicht-so-guten-umgang-mit-passwortern/<p>Seit Längerem wollte ich schon über den guten und nicht so guten Umgang mit Passwörtern bzw. der Authentifizierung im Allgemeinen schreiben. <br />
Hier sind nun meine Überlegungen, natürlich auch mit Verweisen. <br />
Hoffentlich kann es dem einen oder anderen bei der Applikationsentwicklung und dem Umgang mit Passwörtern helfen.</p>
<span id="continue-reading"></span><br>
<h2 id="allgemein">Allgemein</h2>
<p>Der Umgang mit Passwörtern kann aus mindestens zwei Perspektiven betrachtet werden. </p>
<p>Zum einen gibt es den Anwender, also derjenige, der ein Passwort (mehrere!) verwendet und es sich irgendwie merken muss. </p>
<p>Zum anderen sind es die Anwendungen bzw. deren Entwickler, die die Authentifizierung durchführen, Passwörter speichern und dafür sorgen müssen, dass gute Passwörter genutzt werden und sicher damit umgegangen wird. </p>
<p>Die Kriterien für den Umgang mit Passwörtern überschneiden sich teilweise bei beiden Gruppen, es gibt aber einige spezifische Kriterien. </p>
<p>Der Einfachheit halber habe ich hier zwei Listen für die Applikationssicht und Benutzersicht aufgestellt.
Auf detaillierte Beschreibungen der Grundlagen habe ich aus zeitlichen Gründen verzichtet.
Sofern keine Links dabei sind, handelt es sich um meine Erfahrungen und/oder Schlussfolgerungen. </p>
<p>Die Reihenfolge spiegelt keine Prioritäten wider, die kann auch je nach Anwendung und benötigter Sicherheit unterschiedlich ausfallen.</p>
<p>Außerdem möchte ich betonen, dass es keine absolute Sicherheit gibt.
Die folgenden Punkte helfen daher, das Sicherheitsniveau zu verbessern und es möglichen Angreifern schwerer zu machen.
Daneben spielt allerdings auch noch eine sorgfältige Applikationsimplementierung eine große Rolle.
<br><br></p>
<h2 id="gute-passwortanforderungen-aus-entwicklersicht">Gute Passwortanforderungen aus Entwicklersicht</h2>
<ol>
<li><strong>Beliebige maximale Länge</strong> (mind. 100 chars) des Passworts, es spielt für eine Hash (was hoffentlich zum Speichern genutzt wird!) einfach keine Rolle, wie lang das Passwort ist. Und viel macht das für einen Request auch nicht aus. Dafür erhöht es die <a href="https://en.wikipedia.org/wiki/Password_strength#Entropy_as_a_measure_of_password_strength">potentielle Entropie</a> und ermöglicht die Verwendung besserer Passwörter. Allgemein gilt, dass die Entropie mit der Länge des Passworts steigt, weshalb lange Passwörter/Passphrases fast immer besser sind. Jedenfalls dann, wenn es sich dabei nicht um einfach zu erratende Wörter oder Zahlen- bzw. Buchstabenkombinationen handelt. Am besten sind daher entweder Passphrases mit vielen zufälligen Wörtern oder einer ähnlich langen zufälligen Aneinanderreihung von möglichst viel unterschiedlichen Zeichen.</li>
<li>Den Anwender zur Nutzung von <strong>mindestens acht Zeichen</strong> oder <a href="https://en.wikipedia.org/wiki/Random_password_generator#Type_and_strength_of_password_generated">besser 12 Zeichen</a> ermutigen. Weniger als acht Zeichen sind zu wenig für ein einigermaßen sicheres Passwort, weshalb es sich anbietet, die Verwendung von mindestens acht Zeichen auch zu erzwingen. Aber man will den Anwender auch nicht vergraulen und zufällige Passwörter aus einer Vielfalt an Zeichen sind für viele Anwendungsfälle zumindest sicher genug, daher muss man unter Umständen Kompromisse machen.</li>
<li>Eine weitere häufig genutzte Methode ist es, die <strong>direkte Wiederholung von Zeichen zu verhindern</strong> oder maximal zwei bis drei Wiederholungen zu erlauben. So eine Regel soll simple Passwörter mit einfacher Zeichenwiederholung vermeiden bzw. die Komplexität erhöhen. Allerdings ist hier auch wieder Vorsicht geboten, da einige Benutzer dabei in ihrem Workflow beeinträchtigt sein könnten und manche Wörter auch mal mehrere gleiche Buchstaben hintereinander enthalten können. Daher ist es meistens besser, den Nutzer nur darauf hinzuweisen, dass das Passwort aufgrund dieser Wiederholungen schwächer ist und der Schwerpunkt das Ermöglichen einer möglichst hohen Entropie sein sollte.</li>
<li>Es kann auf den ersten Blick auch Sinn machen, <strong>Groß-/Kleinschreibung</strong> oder die Verwendung von <strong>Sonderzeichen</strong> zu erzwingen. Dabei handelt es sich jedoch auch um eine starke Einschränkung des Anwenders und kann bei diesem trotz Verwendung eines guten Passworts zu unnötigem Ärger führen. Wenn so eine Regel existiert, dann sollte es egal sein, an welcher Stelle (ja, so etwas habe ich schon erlebt...) und jeweils nur mindestens ein Mal. Der Schwerpunkt sollte aber eher auf der Länge als auf Sonderzeichen liegen. Eine Alternative ist auch hier, den User lediglich darauf hinzuweisen, dass das Passwort mit einer geringen Zeichenauswahl möglicherweise unsicher ist.</li>
<li><a href="https://github.com/dropbox/zxcvbn"><strong>Ein Strength Meter</strong></a> kann insbesondere bei den vorherigen drei Punkten helfen. Es sollte sich in erster Linie auf die Entropie und dann auf Komplexität beziehen und möglichst vorhandene blacklists berücksichtigen. Will man auch noch sonstige prinzipiell einfach zu ratende Passwörter abdecken, wird es irgendwann ziemlich kompliziert. Solch eine Passwortbewertung sollte also gut durchdacht sein und dem Anwender möglichst viel Hilfestellung und Freiheitsgrade bei der Wahl des Passworts bieten. Allerdings sind solche Hilfen nicht unbedingt barrierefrei und funktionieren meist nicht für Nutzer, die JavaScript deaktiviert haben. Sind all diese Punkte bedacht, ist ein Strength Meter auf jeden Fall erzwungenen Regeln aus den vorherigen zwei Punkten vorzuziehen. Es lässt dem Nutzer die Freiheit und zeigt trotzdem an, ob das Passwort den Sicherheitsansprüchen genügt.</li>
<li>Keine Regeln, die <strong>bestimmte Zeichen an einer bestimmten Stelle</strong> erfordern. Das ist meiner Meinung nach eine der blödesten Möglichkeiten, die Nutzung eines sicheren Passworts zu "unterstützen". Das Gegenteil dürfte der Fall sein, besonders, wenn der User zufallsgenerierte Passwörter verwenden will.</li>
<li><strong>Leerzeichen und mindestens kompletten ASCII Satz</strong> erlauben, gut sind auch Umlaute und Sonderzeichen oder Schriftzeichen aus einem anderen Alphabet. Abgesehen von einem etwas gründlicherem Stringhandling spricht auch absolut nichts dagegen. Dafür erhält man deutlich mehr Komplexität und damit zumindest die Grundlage für sichere Passwörter.</li>
<li>Außer für besonders sicherheitskritische Anwendungen braucht es <a href="https://www.schneier.com/blog/archives/2010/11/changing_passwo.html">keine Regeln, dass das Passwort <strong>nach einer bestimmten Zeit geändert</strong></a> werden muss. Solch eine policy führt leider oft entweder zu schlechteren Passwörtern oder das regelmäßige Vergessen durch den Benutzer. Besser ist es, ein gutes Monitoring zu implementieren und einen Hinweis zu schicken bei einem Verdacht auf Kompromittierung mit einer Anleitung bzw. einem Link zur Änderung des Passworts. Außerdem sollte dem User verdächtiges Verhalten bzw. die Anzahl der (fehlgeschlagenen) Logins angezeigt werden, damit er selbst reagieren kann. Ganz schlimm sind meiner Erfahrung nach Systeme, die die Änderung nur nach Ablauf des Passworts erlauben. Wenn schon solch eine Policy implementiert wurde oder werden muss, dann sollte man das Passwort trotzdem jederzeit ändern können.</li>
<li><a href="https://www.wired.com/2015/07/websites-please-stop-blocking-password-managers-2015/">Keine <strong>anti copy-paste</strong></a> (<a href="https://www.ncsc.gov.uk/blog-post/let-them-paste-passwords">SSP</a>) Regel verwenden. Wenn es dank irgendwelcher Policies doch unbedingt nötig sein sollte, dann sollte man das klar kommunizieren und nicht einfach z. B. das Eingabefeld leeren. Für Nutzer mit Passwordmanager z.B. ist das sehr umständlich. Man will das Passwort nach Möglichkeit nun mal nicht eintippen, wenn man es sich nicht merken kann und es sowieso im Manager gespeichert oder on the fly generiert wird.</li>
<li>Verwendung eines aktuellen <strong>öffentlichen hash- bzw. KDF-Verfahrens</strong> wie <a href="https://en.wikipedia.org/wiki/Bcrypt">bcrypt</a>, <a href="https://en.wikipedia.org/wiki/Scrypt">scrypt</a> oder <a href="https://en.wikipedia.org/wiki/Argon2">argon2</a> im Backend mit individuellen salt und möglichst hoher Iteration. Wenn man zumindest eine (gut implementierte) Kryptographie auf aktuellem Stand verwendet, sind die Passwörter selbst nach einem Hack der Anwendung noch (relativ) sicher.</li>
<li>Als Zusatz kann man auf Hilfen wie <strong>Passwort Manager oder Generatoren</strong> hinweisen. Bei der Angabe von spezifischen Tools muss die Liste allerdings regelmäßig gepflegt werden, da sich einige Programme bzw. Versionen in der Vergangenheit als unsicher herausgestellt haben.</li>
<li><a href="https://fidoalliance.org/about/what-is-fido/">Fido</a> als <a href="https://de.wikipedia.org/wiki/Zwei-Faktor-Authentifizierung"><strong>"two-factor"</strong></a> authentication implementieren. Hier ist die Verbreitung der Endanwender noch nicht so hoch, es hilft aber für die mehr technikaffine Kundschaft. Alternativ ist die "two-factor authentication" über <a href="https://en.wikipedia.org/wiki/Multi-factor_authentication#Mobile_phone_two-factor_authentication">App oder SMS</a> möglich. Allgemein sollte eine 2FA auf jeden Fall angeboten oder sogar als Vorgabe implementiert werden.</li>
<li>Ein sicherer <strong>"Passwort vergessen"</strong> Prozess (2FA, E-Mail Link, SMS) erleichtert dem User die Nutzung, falls er sein Passwort vergessen sollte. Sicherheitsfragen werden vom Nutzer leider meistens <a href="https://security.googleblog.com/2015/05/new-research-some-tough-questions-for.html">vergessen und sind für den Attacker häufig leicht zu schätzen</a>.</li>
<li>Die Verwendung einer <a href="https://en.wikipedia.org/wiki/Blacklist_(computing)#Usernames_and_passwords"><strong>Passwort blacklist</strong></a> kann hilfreich sein, um User zur Verwendung von sichereren Passwörtern zu ermuntern bzw. die Verwendung von unsicheren Passwörtern zu verhindern. Hier ist aber auch wieder Vorsicht geboten, zuviele blockierte Passwörter können einen potentiellen Benutzer wiederum abschrecken.</li>
<li>Eine weitere Hilfe gegen Einbrüche ist das Reduzieren der <strong>maximalen Loginversuche</strong> innerhalb eines bestimmten Zeitraumes bzw. einer Sperrung des Accounts für einen zufälligen Zeitraum bei zuviel (z. B. 3) Fehlschlägen hintereinander.</li>
<li>Die Übertragung sollte verschlüsselt mit Hilfe von <strong>TLS/SSL</strong> erfolgen um man-in-the-middle Angriffe und das Erkennen von sensitiven Benutzerdaten wie Passwörtern zu erschweren.
<br><br></li>
</ol>
<h2 id="gute-passworter-aus-benutzersicht">Gute Passwörter aus Benutzersicht</h2>
<ol>
<li>Die Verwendung eines sicheren <strong>Passwort Managers und Generators</strong> ist hilfreich, bei vielen genutzten Diensten mit Authentifizierung sogar im Grunde Voraussetzung. Ein Passwort Manager deshalb, weil es das Verwalten (bzw. "Merken") der Passwörter enorm erleichtert, da man sich nicht alle Passwörter merken muss. Und ein (random) Passwort Generator, weil <a href="https://en.wikipedia.org/wiki/Random_password_generator#Type_and_strength_of_password_generated">zufällige Passwörter tendenziell deutlich sicherer sind</a> und zwar je mehr, desto größer die Zahl der zu Verfügung stehenden unterschiedlichen Zeichen ist. Meiner Meinung nach sollte man auf <a href="https://bgrande.de/page/easy-password-handler.html">Passwort Manager ohne einen Passwortsafe</a> zurückgreifen. Ein gut implementierter Generator mit spezifischen Salt und Masterpassphrase reduziert zumindest das Risiko, dass alle Passwörter zugänglich werden, sollte der Safe geknackt werden oder ein Bug darin für unberechtigten Zugang sorgen.</li>
<li>Zwingend ist die Regel, für <strong>jeden Dienst ein anderes Passwort bzw. Passphrase</strong> zu verwenden. Wird ein Passwort durch einen Hack oder sonstigen Zugang geknackt, so ist nur dieser eine Dienst bzw. eine Anwendung davon betroffen. Hier hilft wieder ein Passwort Manager.</li>
<li><strong>Mindestens 12 Zeichen</strong> inklusive Sonderzeichen, Groß- und Kleinschreibung, Zahlen, Leerzeichen usw. sollten für ein zufälliges Passwort verwendet werden. Oder man verwendet lange Passphrases bzw. <a href="https://en.wikipedia.org/wiki/Diceware">Diceware</a>.</li>
<li>Die <strong>Masterphrase</strong> für einen Passwortsafe oder <a href="https://bgrande.de/page/easy-password-handler.html">stateless generierte Passwörter</a> sollte mindestens 20 Zeichen (besser deutlich mehr) haben und eine hohe Entropie aufweisen. Siehe auch der vorherige Punkt.</li>
<li>Ein Passwort für einen möglicherweise <strong>kompromittierten Dienst</strong> sollte sofort gewechselt werden. Es besteht immerhin die Chance, dass der Angreifer noch keinen Zugriff vorgenommen hat. Je schneller man reagiert, desto besser.</li>
<li>Wenn <strong>"two-factor authentication"</strong> angeboten wird (oder multiple-factor), sollte es auch verwendet werden. 2FA erhöht die Hürde für potentielle Angreifer enorm. Auch <a href="https://en.wikipedia.org/wiki/One-time_password">Einmalpasswörter</a> (als 2FA) sind sinnvoll und machen es potentiellen Angreifern deutlich schwerer.</li>
<li>Ein Passwort ist nur so lange sicher, wie es <strong>nicht weitere Personen</strong> kennen. Man sollte es also nach Möglichkeit niemandem oder im Ausnahmefall nur Personen anvertrauen, denen man sehr großes Vertrauen entgegen bringt.</li>
<li>Ähnlich dem vorherigen Punkt sollte ein Passwort <strong>nirgendwo im Klartext</strong> gespeichert oder aufgeschrieben werden.
<br><br> </li>
</ol>
<h2 id="umgang-mit-accountdaten-und-biometrie">Umgang mit Accountdaten und Biometrie</h2>
<ol>
<li>Es kann helfen, pro Dienst eine eindeutig der Anwendung <strong>zuordenbare E-Mail-Adresse</strong> zu verwenden (die ausschließlich dort verwendet wird, z. B. dienstname@domain.tld). Neben einfacheren Filtern ist Spam auf diese Adresse ein Hinweis, dass etwas nicht stimmt. Entweder verkauft der Anbieter die Daten seiner Kunden oder jemand kam unbefugt an die Userdaten, zumindest aber die E-Mail-Adressen. Das ist besonders dann verdächtig, wenn auch der möglicherweise hinterlegte Name korrekt verwendet wird. Des Weiteren erschwert es einem möglichen Angreifer die automatische Zuordnung von Accounts bei anderen Diensten und liefert dadurch weniger Ansatzpunkte beim Knacken der Passwörter.</li>
<li><strong>Biometrische Authentifizierung</strong> ist ein zweischneidiges Schwert. Zum einen vereinfacht es die Authentifizierung für den User enorm, sofern die nötige Hardware vorhanden ist. Man muss sich, sofern keine Kombination mit einem Passwort verwendet wird, kein Passwort merken und hat die Authentifizierungsmerkmale immer dabei. Allerdings reicht eine mangelhafte Implementierung und man kann davon ausgeben, dass es die gibt/geben wird und ein Einbruch, dann ist das genutzte Merkmal nicht <a href="http://www.ccc.de/de/updates/2013/ccc-breaks-apple-touchid">mehr sicher</a>, da es sich <a href="https://www.wired.com/2012/07/reverse-engineering-iris-scans/">sogar reverse engineeren lässt</a>. Daneben kann man <a href="https://media.ccc.de/v/biometrie-s8-iris-fun">Fotos</a> bzw. einen <a href="https://www.heise.de/security/meldung/31C3-CCC-Tueftler-hackt-Merkels-Iris-und-von-der-Leyens-Fingerabdruck-2506929.html">künstlichen Fingerprint</a> der betreffenden Personen für eine Authentifizierung nutzen. Wirklich sicher ist so eine biometrische Authentifizierung nur in Kombination mit einem Passwort. Dann fallen allerdings auch die Vorteile weg. <em>Update 13.11.2018:</em> <em>Auch <a href="https://arxiv.org/abs/1705.07386" title="DeepMasterPrints">die Generierung von MasterPrints</a> mittels Deep Learning (<a href="https://en.wikipedia.org/wiki/Generative_adversarial_network" title="generative adversarial network">GAN</a>) scheint hier eine Option zu sein.</em></li>
</ol>
<p>Weitere Erklärungen und Argumente liefert die <a href="https://www.ncsc.gov.uk/guidance/password-guidance-simplifying-your-approach">britische NCSC</a> (und ja, ich weiß, dass die Teil des GCHQ sind, aber der Beitrag ist trotzdem gut!). Kommentare (derzeit noch per Mail oder twitter) sind gerne willkommen.</p>
Let's encrypt mit Hosteurope Webpack nutzen2017-02-18T22:51:48+00:002020-04-29T14:27:41+00:00https://bgrande.de/blog/let-s-encrypt-mit-hosteurope-webpack-nutzen/<p>Da hosteurope leider bisher nicht let's encrypt für die webpack- oder andere managed Pakete in automatisierter Form anbietet, <br />
muss man sich mit einem manuellen Erstellen des account-keys sowie dem Zertifikat behelfen. <br />
Wie das geht, was dabei zu beachten ist und wie man das zumindest zum Teil automatisieren kann, findet sich in diesem Artikel.</p>
<span id="continue-reading"></span><br>
<h2 id="verwendung-des-let-s-encrypt-certbot-tools">Verwendung des let's encrypt certbot tools</h2>
<p>Als erstes benötigt man certbot (certbot-auto) oder eine <a href="https://letsencrypt.org/docs/client-options/">andere Alternative</a> für den acme-Client. Eine Anleitung findet sich bei <a href="https://certbot.eff.org/docs/install.html#certbot-auto">let's encrypt</a>.</p>
<p>Der Einfachheit halber habe ich mit dem python-tool certbot begonnen. Es ist leider etwas schwierig, alle Parameter irgendwo dokumentiert zu finden, weshalb sich ein initiales Ausführen des certbot-auto clients empfiehlt um die Abhängigkeiten aufzulösen.</p>
<p>Oder man führt das script <a href="https://bgrande.de/blog/let-s-encrypt-mit-hosteurope-webpack-nutzen/./get-certificate">get-certificate</a> aus, das ich für die Automatisierung geschrieben habe. Mein Workflow ist wie folgt:</p>
<ol>
<li>Ich möchte das let's encrpyt Zertifikat mit 4096 Bit anstatt der voreingestellten 2048 erstellen.</li>
<li>Da ich mehrere Domains auf dem webpack laufen habe, möchte ich die auch alle im Zertifikat einbinden.</li>
<li>Standardmäßig speichert das certbot script die erzeugten Keys, Zertifikate und sonstige dazugehörigen (Meta-)Dateien in einer eigenen Folderstruktur unter '/etc/letsencrypt/'. Das macht auch für den Einsatz auf dem Produktivserver durchaus Sinn, weniger aber, wenn es ein lokales setup ist, auf dem das Zertifikat nicht deployed werden soll. Daher hätte ich die erzeugten Dateien gerne an einem Ort meiner Wahl.</li>
<li>Reiner text modus anstatt ncurses erscheint mir für solch ein script besser geeignet.</li>
<li>Alles, was sich ohne Interaktion machen lässt, soll auch genutzt werden.</li>
</ol>
<p>Bis auf Punkt 3 lässt sich das schön über ein config file umsetzen, dass über den parameter <code>--config pfad/zur/config.ini</code> eingebunden wird. Theoretisch gibt es auch Parameter, um zumindest die Zielpfade für die Zertifikate anpassen lassen: <code>--cert-path pfad/zu/cert.pem</code>, <code>--chain-path pfad/zu/chain.pem</code>, <code>--fullchain-path pfad/zu/fullchain.pem</code>. Leider funktionieren die aber nur in Verbindung mit dem Parameter <code>--csr</code>, der es ermöglicht, einen selbst erstellten account-key sowie eine selbst erstellte <a href="https://www.sslshopper.com/what-is-a-csr-certificate-signing-request.html">Certificate Signing Request</a> zu verwenden. Das ist leider nicht ganz eindeutig ersichtlich, weshalb ich es ausprobiert habe und mit einem timeout bei der Verifizierung gescheitert bin. Das gibt wiederum wenig Hinweise auf die eigentliche Ursache, lag dann aber irgendwie nahe.
<br><br></p>
<h3 id="das-let-s-encrypt-configfile">Das let's encrypt configfile</h3>
<p>Punkt 1, 2, 4 und 5 lassen sich über ein <a href="https://bgrande.de/blog/let-s-encrypt-mit-hosteurope-webpack-nutzen/./acme-ini">.ini config file</a> erledigen.</p>
<p>Die folgenden Parameter MÜSSEN geändert werden, damit das Erstellen des let's encrypt Zertifikats für Hosteurope korrekt funktioniert:</p>
<ol>
<li>email: Muss eine E-Mail-Adresse unter Kontrolle des Skriptausführenden sein</li>
<li>domains: Muss eine oder mehrere (Komma-separierte) Domains unter der Kontrolle des Skriptausführenden sein</li>
</ol>
<p>Für meinen Workflow MUSS die INI-Datei in ein (zu erstellendes) Verzeichnis im Skriptroot (also parallel zum Skript) kopiert, angepasst und '<em>acme.ini</em>' genannt werden. Das Verzeichnis MUSS exakt mit dem Namen der ersten Domain aus der domains-Liste der Konfiguration übereinstimmen.</p>
<p>Über <code>rsa-key-size</code> kann die Bitgröße des RSA-Keys bestimmt werden.</p>
<p>Nutzt man <code>authenticator = webroot</code> kann in Verbindung mit <code>webroot-path = /path/to/your/temp/mount/target</code> eine Automatisierung erreicht werden, sofern man über z. B. <code>curlftpfs</code> das Webroot-Verzeichnis mounted.</p>
<p>Um automatisch die AGB zu bestätigen (tut man es nicht, bricht das Skript ab), kann <code>agree-tos</code> verwendet werden.</p>
<p>Nicht unbedingt notwendig für den manual-mode, sofern man das Skript händisch ausführt, ist die Option <code>keep</code>, die bestimmt, dass der Key nicht automatisch überschrieben wird, sofern er noch gültig ist.</p>
<p>Setzt man <code>text</code> auf True, wird statt ncurses der simple text-mode genutzt.</p>
<p>Außerdem kann über <code>preferred-challenges</code> die bevorzugte Verifizierungsart gewählt werden (dns oder http).</p>
<p>Eine weitere zwingende Bestätigung muss für das Loggen der IP-Adresse getätigt werden, was über die Option <code>manual-public-ip-logging-ok</code> (ohne Parameter) umgesetzt werden kann.
<br><br></p>
<h3 id="die-verwendeten-let-s-encrypt-parameter">Die verwendeten let's encrypt Parameter</h3>
<p>Wichtig für die <a href="https://thomas-leister.de/lets-encrypt-zertifikate-im-manual-mode-abholen/">manuelle Erstellung</a> des Zertifikats sind folgende Parameter:</p>
<ul>
<li><code>certonly</code>: Sorgt dafür, dass nur das Zertifikat erstellt wird, führt aber kein deployment und config update</li>
<li><code>-a manual</code>: Legt den manuellen Modus fest</li>
<li><code>--config pfad/zu/acme.ini</code>: Legt den Pfad zum Config-File (siehe oben) fest</li>
<li><code>--logs-dir pfad/zu/logverzeichnis</code>: Optional, wenn man den Logpfad in einem bestimmtem Verzeichnis haben möchte
<br><br></li>
</ul>
<h3 id="erstellen-und-validieren-des-let-s-encrypt-zertifikats">Erstellen und Validieren des let's encrypt Zertifikats</h3>
<p>Hier darf man Unter Umständen Datei-Disc-Jokey spielen... Je nach Menge der Domains müssen einige Verzeichniss und Dateien im .well-known/acme-challenge-Verzeichnis auf dem Zielserver (FTP) erstellt werden. Das nimmt bei mehreren Domains einige Zeit in Anspruch und ist leider redundant.</p>
<p>Eine Automatisierung via webmount des webroots in das lokale Zielverzeichnis ist auch über das oben erwähnte <a href="https://bgrande.de/blog/let-s-encrypt-mit-hosteurope-webpack-nutzen/./get-certificate">script</a> möglich. So bleibt einem das copy and paste erspart und statt ca. 10 Minuten dauert das ganze nur noch ca. 1-2 Minuten und bis auf den Upload benötigt man keine manuelle Interaktion mehr.## Einbinden bei Hosteurope</p>
<p>Mittlerweile ist es sogar möglich, für jede (Sub-)Domain ein eigenes Zertifikat zu hinterlegen. Als ich mit meiner Konfiguration begonnen hatte, gab es die Möglichkeit noch nicht.</p>
<p>Im Hosteurope KIS <code>Webhosting</code>, <code>konfigurieren</code>, <code>Sicherheit & SSL/SSL administrieren</code> wählen, dort das globale Zertifikat setzen.</p>
<p>Als Nächstes müssen der Key (<em>privkey.pem</em>) als auch das Zertifikat (<em>cert.pem</em>) hochgeladen werden. Optional kann auch noch das let's encrypt intermediate Zertifikat hochgeladen werden (<em>intermediate.pem</em>). Eine genaue Anleitung gibt es auch bei <a href="https://www.hosteurope.de/faq/software-services/ssl-zertifikate/ssl-zertifikat-einbinden-webhosting/">hosteurope</a>.</p>
<p>Nach dem Upload sollte es relativ schnell gehen und die domains sind mit SSL erreichbar.</p>
<h2 id="renew-nach-90-tagen-oder-fruher">Renew nach 90 Tagen (oder früher)</h2>
<p>Da der manual mode verwendet wird und damit kein automatisches Erneuern des Zertifikats möglich ist, kann der <code>renew</code>-Parameter des certbot scripts leider nicht verwendet werden. Hier muss genau der gleiche Ablauf wie oben beschrieben durchgeführt werden, also:</p>
<ol>
<li>Ausführen des scripts mit den verwendeten Parametern</li>
<li>Validieren der einzelnen (Sub-)Domains mit Hilfe der .well-known-files</li>
<li>Nach erfolgreicher Validierung Upload bei Hosteurope</li>
</ol>
<p>22.10.2018 - Beschreibung einer erweiterten Automatisierung inklusive automount hinzugefügt</p>
Und das meistgenutzte Passwort ist?2016-12-29T22:53:42+00:002017-03-24T06:58:30+00:00https://bgrande.de/blog/und-das-meistgenutzte-passwort-ist/<p>Ähnliche Auswertungen oder Forschungen gab es ja schon häufiger, aber ich finde, man kann nicht häufig genug darauf hinweisen, welches die am meisten genutzten Passwörter sind. <br />
Und wenn man sich so im Bekanntenkreis umhört (bzw. es teilweise unfreiwillig erzählt bekommt), dann sind die Ergebnisse ganz gut nachzuvollziehen...</p>
<span id="continue-reading"></span><br>
<p><a href="http://www.golem.de/news/auswertung-hallo-ist-deutschlands-meistgenutztes-passwort-1612-125196.html">Eine Auswertung des Hasso-Plattner-Institut (HPI)</a> von unterschiedlichen Datensätzen zur Verwendung von Passwörtern hat ein eigentlich schon fast beängstigendes Ergebnis zu Tage gebracht: "hallo" ist z.B. das am meisten genutzte Passwort in Deutschland. Gefolgt von anderen einfachen "first guess" Passwörtern wie "passwort" oder "123456", welches sich weltweit noch größerer Beliebtheit erfreut. </p>
<p>Daher meine Bitte: Verwendet etwas komplexere Passwörter oder Passphrases (also Sätze, wenn möglich auch mit Leerzeichen, Punkt oder Komma) oder verwendet gleich ein tool wie den <a href="/page/easy-password-handler.html">Easy Password Handler</a>!</p>
Ubuntu Chromium Profile broken2016-11-26T14:25:13+00:002017-03-24T06:58:41+00:00https://bgrande.de/blog/ubuntu-chromium-profile-broken/<p>Since Chrome/Chromium sometimes does not seem to handle profiles correctly, I had trouble making the Browser usable again without losing all of my settings. Here are some tips how it might work.</p>
<span id="continue-reading"></span><br>
<p>If you are using chromium with ubuntu and you ever had an error message stating that your chromium user profile could not be loaded respectively opened... The german message looks like this:</p>
<blockquote>
<p>Ihr Profil konnte nicht ordnungsgemäß geöffnet werden. Möglicherweise stehen nicht alle Funktionen zur Verfügung. Überprüfen Sie, ob das Profil vorhanden ist und Sie Lese- und Schreibzugriff für die Profilinhalte besitzen.</p>
</blockquote>
<br>
The english message should look like this:
<blockquote>
<p>Your profile could not be opened correctly. ....</p>
</blockquote>
<p>You might wonder (like I did) and try some cache invalidation and so on. It didn't help... </p>
<p>And checking write and read access didn't help either.
<br><br></p>
<h2 id="chromium-profile-fix">Chromium Profile Fix</h2>
<p>I tried a few things...
<br><br></p>
<h3 id="what-i-thought-would-work">What I thought would work</h3>
<p>go to '~/.config/chromium/YourProfile and delete all caches from subdirectories like Application Cache/Cache or Storage/ext/chrome-signin/def/Cache/.
<br><br></p>
<h3 id="what-helped-so-far">What helped (so far)</h3>
<p>removing (backup before removing) the whole ~/.config/chromium/ directory and let chromium (after restart) create the new settings. <strong>WARNING</strong>: if you do not use your google account to sync your settings and so on you will have to install and configure it manually, again.
<br><br></p>
<h3 id="what-could-help">What could help</h3>
<p><a href="http://forum.ubuntuusers.de/topic/google-chrome-kann-profil-nicht-ordnungsgemae/2/">Delete (or rename) the file</a> ~/.config/chromium/Default/History.</p>
Easy Password Handler2016-11-23T22:11:15+00:002022-10-03T08:32:33+00:00https://bgrande.de/projects/easy-password-handler/<p>I wrote a Google Chrome Browser extension as well as an Android App which enable the user to handle more secure passwords with more ease. It's based on J. Coglan's great work you can find at getvau.lt. </p>
<span id="continue-reading"></span><br>
<div style="position: absolute; top: 0; right: 0;">
<p><a href="https://github.com/bgrande/pwgenerator-extension/fork"><img src="https://camo.githubusercontent.com/e7bbb0521b397edbd5fe43e7f760759336b5e05f/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f677265656e5f3030373230302e706e67" alt="Fork me on GitHub" /></a></p>
</div>
<h1 id="tl-dr">tl;dr</h1>
<p>I wrote a Chrome Browser extension which enables the user to handle more secure passwords with ease. It's based on J. Coglan's great work <a href="https://getvau.lt/" title="Vault password generator">getvau.lt</a></p>
<p>You can find it at <a href="https://github.com/bgrande/vault-pwgenerator-extension" title="password generator extension at github">github</a>. And you can also <a href="https://chrome.google.com/webstore/detail/password-generator/fobokeococnhobjlfpkoibpjnajichnj" title="Easy Password Handler Chrome Extension">download the extension</a> via the chrome extension store.
<br><br></p>
<h1 id="easy-and-save-web-service-passwords">Easy and save web service passwords</h1>
<p>After different attacks and breaches like the Adobe hack I wanted to find and use a more suitable way to use secure and easy to remind passwords. Many people out there are either using unsecure and easy to guess passwords or (including me until now) try to use a system which derives some kind of service specific passwords from a master password or passphrase. It includes rules like adding servicename parts, numbers and other characters in a special predefined way. Whilst having unsecure passwords is bad for security, the alternative messes with your brain… You only remember the passwords you use often, most of them have to be rebuilt by your rule everytime you have to use it. Another way is to keep it <a href="http://xkcd.com/936/" title="xkcd password strength">like xkcd</a> and use more memorable passphrases. But still you need more (in most cases way more) than one password to remember.</p>
<p>Tools like <a href="http://keepass.info/" title="Keypass">KeyPass</a> can save you the effort of remembering more than one password or passphrase and nevertheless having a different password for every service. But still you have to store these passwords somewhere. So in the (luckily unlikely) case if someone cracked your key’s passphrase (or what's more likely the password safe's implementation) he or she could get all your passwords at once. Furthermore you have to install and use this tool on every device you are using as well as synchronising the password safe itself throughout the different devices.
<br><br></p>
<h1 id="comfort-and-security">Comfort and security</h1>
<p>Such tools (and others) also are able to generate passwords for a new service if you like. These passwords you cannot remember at all (ok, maybe one or two, but that’s it). And most of them are (at least the good ones) hard to guess for a computer, too.</p>
<p>Now what if you would regenerate the password everytime you have to use it based on your passphrase and a service specific salt? First, you would still need a tool executing that job. A (web) service providing only client side (javascript) code serving a form to generate the password would not even limit you to an active internet connection and could be used within every browser. And yes, you should make sure that there is no (asynchronous) network connection, though. You might use an algorithm like <a href="http://en.wikipedia.org/wiki/PBKDF2" title="PBKDF2">PBKDF2</a> or <a href="http://en.wikipedia.org/wiki/Bcrypt" title="bcrypt">bcrypt</a> fulfilling that job. Even if an attacker cracks the service’s database and reaps your password (which normally should be stored as a hopefully salted hash there!) you only have to change this service’s password. Which you can achieve easily by changing the salt (or the passphrase).</p>
<p>Tools like <a href="http://supergenpass.com/" title="supergenpass">supergenpass</a>, <a href="https://getvau.lt/" title="Vault password generator">getvau.lt</a> and <a href="http://mawud.com/" title="mawud">mawud</a> can do that job for you. For more explanation and comparison you might want to <a href="http://ss64.com/docs/security.html">visit ss64.com</a>.</p>
<p>I like getvault the most which is <a href="https://github.com/jcoglan/vault" title="vault at github">available at github</a>, too. So I took the vault library and wrote a <a href="https://chrome.google.com/webstore/detail/password-generator/fobokeococnhobjlfpkoibpjnajichnj">chrome extension</a> called Easy Password Handler providing a popup for every password field, enabling me (and maybe you) using the password generator within the site at the place you need it.
<br><br></p>
<h1 id="how-it-works">How it works</h1>
<p>There still are some issues with special password rules for some services which is by now solved by a short password service rule or a configurable character overwrite as part of the Easy Password Handler overlay.</p>
<p><a href="https://bgrande.de/projects/easy-password-handler/pwgen2-amazon.png"><img src="https://bgrande.de/projects/easy-password-handler/pwgen2-amazon.png" alt="Easy Password Handler" /></a> Allowed or required characters, the desired password length, the default salt method (like prefix, suffix or loginname) and auto submit after generating the password can be configured through the extension’s options. With a current update, using password rules for specific site and creating them by yourself is also possible.</p>
<p>For now it’s chrome/chromium only but I will work on Firefox and Safari support as soon I will have some spare time.</p>
<p>I implemented an <a href="https://github.com/bgrande/pwgenerator-android" title="Easy Password Generator for android on github">android application</a> based on the Easy Password Handler extension as well and published it <a href="https://play.google.com/store/apps/details?id=bgrande.pwgenerator_android" title="Easy Password Generator for android"> in the play store</a>.</p>
HTPC2016-11-23T22:11:15+00:002017-03-24T08:32:24+00:00https://bgrande.de/projects/htpc/<p>For a long time I always had an HTPC as a hobby. I built multiple versions. Here is a description of my last one. </p>
<span id="continue-reading"></span>
<ul>
<li><a href="https://bgrande.de/projects/htpc/#specification">Specification</a></li>
<li><a href="https://bgrande.de/projects/htpc/#description">Installation & Configuration</a></li>
<li><a href="https://bgrande.de/projects/htpc/#links">Sources & Links</a></li>
<li><a href="https://bgrande.de/projects/htpc/#todo">Todo</a>
<br></li>
</ul>
<div class="subsection">
<h2 id="specification"><a name="specification"></a>Specification</h2>
<dl>
<dt style="font-weight: bold;">Processor</dt><dd>AMD E-350</dd>
<dt>Linux Support</dt><dd>good</dd>
<dt>Identification/Module</dt><dd>AMD E-350 1600 MHz Dual Core</dd>
<dt>Comment</dt><dd>surprisingly fast and energy saving</dd>
</dl>
<dl>
<dt style="font-weight: bold;">Memory</dt><dd>4 GB</dd>
<dt>Linux Support</dt><dd>good</dd>
<dt>Identification/Module</dt><dd></dd>
<dt>Comment</dt><dd></dd>
</dl>
<dl>
<dt style="font-weight: bold;">Video Card</dt><dd>ATI Wrestler – AMD Radeon HD 6310</dd>
<dt>Linux Support</dt>
<dd>
<span style="text-decoration: line-through;">Some inconsistent repainting issues with mythtv</span>
<br>should be able to comsume less power
</dd>
<dt>Identification/Module</dt><dd>fglrx</dd>
<dt>Comment</dt><dd><code>export LIBGL_ALWAYS_INDIRECT=1</code> and maybe <code>export XLIB_SKIP_ARGB_VISUALS=1</code> before starting mythtv helps about the repainting issue</dd>
</dl>
<dl>
<dt style="font-weight: bold;">Harddisks</dt>
<dd>
<p>Corsair Accelerator SSD 30GB</p>
<p>SAMSUNG HD502IJ 500GB</p>
<p>SAMSUNG HD252KJ 250GB</p>
</dd>
<dt>Linux Support</dt>
<dd>good</dd>
<dt>Identification/Module</dt><dd>SATA ahci</dd>
<dt>Comment</dt><dd>The SSD is for fast booting (use mount options: noatime,discard), 500 GB HD internal data storage, 250 GB HD external data storage</dd>
</dl>
<dl>
<dt style="font-weight: bold;">Wifi</dt>
<dd>TP-Link (TL-WN721N) – 150Mbps WLAN</dd>
<dt>Linux Support</dt>
<dd>good</dd>
<dt>Identification/Module</dt><dd>atheros – ath9k_htc</dd>
<dt>Comment</dt><dd></dd>
</dl>
<dl>
<dt style="font-weight: bold;">LAN</dt>
<dd>1 GBit Realtek</dd>
<dt>Linux Support</dt>
<dd>good</dd>
<dt>Identification/Module</dt><dd>RTL8111/8168B – r8169</dd>
<dt>Comment</dt><dd></dd>
</dl>
<dl>
<dt style="font-weight: bold;">Display</dt>
<dd>Size 24″<br>Resolution 1980×1080<br>non-glare</dd>
<dt>Linux Support</dt>
<dd>good</dd>
<dt>Identification/Module</dt><dd></dd>
<dt>Comment</dt><dd>Display is connected through hdmi<br>everything works fine</dd>
</dl>
<dl>
<dt style="font-weight: bold;">Remote Control</dt>
<dd>Came with TV Tuner Card</dd>
<dt>Linux Support</dt>
<dd>good</dd>
<dt>Identification/Module</dt><dd>anysee_rc, …</dd>
<dt>Comment</dt><dd></dd>
</dl>
<dl>
<dt style="font-weight: bold;">DVD/CD</dt>
<dd>LiteOn iHAS122</dd>
<dt>Linux Support</dt>
<dd>good</dd>
<dt>Identification/Module</dt><dd></dd>
<dt>Comment</dt><dd></dd>
</dl>
<dl>
<dt style="font-weight: bold;">Audio</dt>
<dd>ATI Wrestler HDMI Audio Radeon HD 6250/6310</dd>
<dt>Linux Support</dt>
<dd>ok – hdmi sound does not work yet</dd>
<dt>Identification/Module</dt><dd>snd_hda_intel</dd>
<dt>Comment</dt><dd></dd>
</dl>
<dl>
<dt style="font-weight: bold;">TV</dt>
<dd>Anysee E7 PTC/PT2C DVB-C Internal USB Card</dd>
<dt>Linux Support</dt>
<dd>can’t compare but very bad receiption with some channels (all but German ARD „group“)</dd>
<dt>Identification/Module</dt><dd>dvb_usb</dd>
<dt>Comment</dt><dd>SD and HD working<br>CI Slot (AlphaCrypt) with SmartCard working</dd>
</dl>
<dl>
<dt style="font-weight: bold;">OS</dt>
<dd>MythUbuntu Linux 12.04 64bit</dd>
<dt>Linux Support</dt>
<dd>good</dd>
<dt>Identification/Module</dt><dd>kernel 3.2 64bit</dd>
<dt>Comment</dt><dd>needs current linux media-build compiled and installed to get the TV card working with CI</dd>
</dl>
<dl>
<dt style="font-weight: bold;">Mythtv</dt>
<dd>Mythtv 0.25</dd>
<dt>Linux Support</dt>
<dd>good</dd>
<dt>Identification/Module</dt><dd>ok, still some issues with vaapi + fglrx + hd playback profiles</dd>
<dt>Comment</dt><dd>0.25 frontend and backend</dd>
</dl>
<hr>
</div>
<br><br>
<h2 id="installation-and-configuration"><a name="description"></a>Installation and Configuration</h2>
<p>No issues while ubuntu installation. Everything works fine there. Installed Mythbuntu to the SSD for faster boot (more waf friendly)</p>
<p>More tbd (some more about autostart/scheduling/shutdown configuration, remote control config, )</p>
<p><br><br></p>
<div class="subsection">
<h2 id="sources-and-links"><a name="links"></a>Sources and Links</h2>
<p>No special order whatsoever…</p>
<dl>
<dt><a href="http://palosaari.fi/linux/">http://palosaari.fi/linux/</a></dt>
<dd>Kernel developer – anysee driver/module and more developer. <strong>Big thanks!</strong></dd>
<dt><a href="http://www.vdr-portal.de/board16-video-disk-recorder/board85-hdtv-dvb-s2/106098-anysee-dvb-s2-t-c/index2.html">http://www.vdr-portal.de/board16-video-disk-recorder/board85-hdtv-dvb-s2/106098-anysee-dvb-s2-t-c/index1.html</a></dt>
<dd>Discussion about Anysee E7 (German)</dd>
<dt><a href="http://olefriis.blogspot.de/2010/06/mythtv-on-eeebox.html">http://olefriis.blogspot.de/2010/06/mythtv-on-eeebox.html</a></dt>
<dd>Blog article about mythtv, eebox and anysee DVB-C</dd>
<dt><a href="http://ubuntuforums.org/showthread.php?t=1385896">http://ubuntuforums.org/showthread.php?t=1385896</a></dt>
<dd>VAAPI with mythtv and ATI/AMD Video cards</dd>
<dt><a href="http://mythtvbox.blogspot.de/2005/12/getting-mythvideo-to-use-169-aspect.html">http://mythtvbox.blogspot.de/2005/12/getting-mythvideo-to-use-169-aspect.html</a></dt>
<dd>Aspect ratio for watching hdtv</dd>
</dl>
</div>
<br>
<h2 id="todo"><a name="todo"></a>Todo</h2>
<h3 id="get-real-5-1-sound-working">get real 5.1 sound working</h3>
Linux on Samsung Chronos 72016-11-23T22:11:15+00:002017-03-24T08:32:17+00:00https://bgrande.de/projects/linux-on-samsung-chronos7/<p>Since using linux on my Samsung Chronos 7 turned out to be painful, here are my ups and downs with explanations or links how to improve the overall performance. </p>
<span id="continue-reading"></span><div style="display: inline-block; padding-top: 1em;">
<p><img src="https://bgrande.de/projects/linux-on-samsung-chronos7/chronos7_small.png" alt="image of samsung chronos 7 with tux on it" /></p>
</div>
<nav style="display: inline-block; margin: 0 2em; vertical-align: top">
<ul>
<li><a href="https://bgrande.de/projects/linux-on-samsung-chronos7/#specification">Specification</a></li>
<li><a href="https://bgrande.de/projects/linux-on-samsung-chronos7/#power">Power usage</a></li>
<li><a href="https://bgrande.de/projects/linux-on-samsung-chronos7/#description">Installation & Configuration</a></li>
<li><a href="https://bgrande.de/projects/linux-on-samsung-chronos7/#downloads">Scripts</a></li>
<li><a href="https://bgrande.de/projects/linux-on-samsung-chronos7/#links">Sources & Links</a></li>
<li><a href="https://bgrande.de/projects/linux-on-samsung-chronos7/#todo">Todo</a></li>
</ul>
</nav>
<br><br>
<div class="subsection">
<h2 id="specification"><a name="specification"></a>Specification</h2>
<dl>
<dt style="font-weight: bold;">Processor</dt><dd>Intel Core i5 2430M</dd>
<dt>Linux Support</dt>
<dd style="background-color: rgba(137, 255, 0, 0.36)">gets very hot sometimes and fan is running almost
all the time which is related to the onchip (and dedicated) video device(s)<br>
<span>Constantly comes down to ~60 C now</span>
</dd>
<dt>Identification/Module</dt><dd>Intel(R) Core(TM) i5-2430M CPU @ 2.40GHz</dd>
<dt>Comment</dt>
<dd>
<p>using the <code>ondemand</code> governor and cpufrequtils</p>
</dd>
</dl>
<dl>
<dt style="font-weight: bold;">Memory</dt><dd>Intel Core i5 2430M</dd>
<dt>Linux Support</dt><dd style="background-color: rgba(137, 255, 0, 0.36)">good</dd>
<dt>Identification/Module</dt><dd></dd>
<dt>Comment</dt><dd></dd>
</dl>
<dl>
<dt style="font-weight: bold;">OnChip Video</dt><dd>Intel HD 3000 256MB</dd>
<dt>Linux Support</dt>
<dd style="background-color: rgba(137, 255, 0, 0.36)">
ok<br>
<span style="text-decoration: line-through;">still having temperature issues</span>
<span>Constantly comes down to ~60 C now but gets very hot as soon as you use movie playback or
other video related applications</span>
</dd>
<dt>Identification/Module</dt><dd>i915</dd>
<dt>Comment</dt>
<dd>
<p>For power saving and reducing heat add the following to your
<code>GRUB_CMDLINE_LINUX_DEFAULT</code>:
<code>pcie_aspm=force <em>acpi=noirq</em> acpi_osi=Linux acpi_backlight=vendor intel_iommu=off i915.modeset=1 i915.i915_enable_rc6=1 i915.lvds_downclock=1</code>;
additionally what seemed to help a bit with temperature issues: <code>i915.semaphores=1</code>
Adding the <code>acpi=noirq</code> might help to really enable rc6.
Do not forget to run <code>update-grub</code> then!
To ensure the gpu does not get too hot try this:
<code>echo 650 &gt; /sys/kernel/debug/dri/0/i915_max_freq</code>.
It sets the max gpu frequency to a user defined frequency
(the lowest possible 650 in my case).
No more fan terror… Downside: playing 3D games will not be much fun from now on…
</dd></p>
</dl>
<dl>
<dt style="font-weight: bold;">Discrete Video</dt>
<dd>AMD Radeon HD 6470M <span style="font-style: italic;">256MB?</span></dd>
<dt>Linux Support</dt>
<dd style="background-color: rgba(255, 209, 16, 0.34)">
<del>turned off</del>
gets too hot as long as you do not change the power profile/method or use the proprietary fglrx<br>
consumes too much power
</dd>
<dt>Identification/Module</dt><dd>radeon/fglrx</dd>
<dt>Comment</dt>
<dd>
<p>To switch it off:
<code>blacklist radeon</code>
and run <code>update-initramfs -u</code>;
switch it off at boot by adding <code>fbcon=map:0</code> to
<code>GRUB_CMDLINE_LINUX_DEFAULT</code>
It might help to switch the power method to dynamic:
<code>echo "dynpm" &gt; /sys/class/drm/card1/device/power_method</code>
or the profile to low: <code>echo "profile" &gt; /sys/class/drm/card1/device/power_method; echo "low" &gt; /sys/class/drm/card0/device/power_profile</code>
</dd></p>
</dl>
<dl>
<dt style="font-weight: bold;">Harddisk</dt>
<dd><span style="font-style: italic;">
<span style="text-decoration: line-through;">1TB Samsung HDD</span><br>
120GB Samsung 830 series SSD</span>
</dd>
<dt>Linux Support</dt><dd style="background-color: rgba(137, 255, 0, 0.36)">good</dd>
<dt>Identification/Module</dt><dd>SATA ahci</dd>
<dt>Comment</dt><dd>very fast</dd>
</dl>
<dl>
<dt style="font-weight: bold;">Wifi</dt><dd>Broadcom 802.11bgn</dd>
<dt>Linux Support</dt><dd style="background-color: rgba(137, 255, 0, 0.36)">No powersave with kernel drivers</dd>
<dt>Identification/Module</dt><dd>BCM43225 – bcma</dd>
<dt>Comment</dt><dd></dd>
</dl>
<dl>
<dt style="font-weight: bold;">Bluetooth</dt><dd>Broadcom ...</dd>
<dt>Linux Support</dt><dd style="background-color: rgba(137, 255, 0, 0.36)">good</dd>
<dt>Identification/Module</dt><dd>btusb</dd>
<dt>Comment</dt><dd></dd>
</dl>
<dl>
<dt style="font-weight: bold;">LAN</dt><dd>1 Gbit</dd>
<dt>Linux Support</dt><dd style="background-color: rgba(137, 255, 0, 0.36)">good</dd>
<dt>Identification/Module</dt><dd>RTL8111/8168B – r8169</dd>
<dt>Comment</dt><dd></dd>
</dl>
<dl>
<dt style="font-weight: bold;">Display</dt>
<dd>
Size 14″<br>
Resolution 1600×900<br>
non-glare
</dd>
<dt>Linux Support</dt><dd style="background-color: rgba(137, 255, 0, 0.36)">good</dd>
<dt>Identification/Module</dt><dd></dd>
<dt>Comment</dt><dd>very bright</dd>
</dl>
<dl>
<dt style="font-weight: bold;">Keyboard</dt><dd>With backlight</dd>
<dt>Linux Support</dt>
<dd style="background-color: rgba(137, 255, 0, 0.36)">
Working<br>
Can’t toggle backlight with FN-Keys
</dd>
<dt>Identification/Module</dt><dd></dd>
<dt>Comment</dt><dd>very bright</dd>
</dl>
<dl>
<dt style="font-weight: bold;">Touchpad</dt><dd>Elantech</dd>
<dt>Linux Support</dt>
<dd style="background-color: rgba(137, 255, 0, 0.36)">
almost perfect<br>
Some gestures missing
</dd>
<dt>Identification/Module</dt><dd></dd>
<dt>Comment</dt><dd></dd>
</dl>
<dl>
<dt style="font-weight: bold;">DVD/CD</dt><dd>Samsung...</dd>
<dt>Linux Support</dt><dd style="background-color: rgba(137, 255, 0, 0.36)">good</dd>
<dt>Identification/Module</dt><dd></dd>
<dt>Comment</dt><dd>with Eject over keyboard</dd>
</dl>
<dl>
<dt style="font-weight: bold;">Audio</dt><dd>Intel HDA</dd>
<dt>Linux Support</dt><dd style="background-color: rgba(137, 255, 0, 0.36)">good</dd>
<dt>Identification/Module</dt><dd>snd-hda-intel</dd>
<dt>Comment</dt><dd></dd>
</dl>
<dl>
<dt style="font-weight: bold;">OS</dt>
<dd>
<span style="text-decoration: line-through;">Windowx 7 Pro 64bit</span><br>
Ubuntu Linux <span style="text-decoration: line-through;">11.10 </span><del>12.04</del> 12.10 64bit
</dd>
<dt>Linux Support</dt><dd style="background-color: rgba(137, 255, 0, 0.36)">good</dd>
<dt>Identification/Module</dt><dd>kernel 3.<span style="text-decoration: line-through;">3</span><del>4</del>5 64bit (Ubuntu Mainline precise)</dd>
<dt>Comment</dt><dd></dd>
</dl>
<hr>
</div>
<div class="subsection">
<h2 id="power-usage"><a name="power"></a>Power usage</h2>
<h3>Battery power consumption (without discrete graphics)</h3>
<dl>
<dt>Type</dt><dd>Standard</dd>
<dt>Usage</dt><dd>~15,5-19 Watt</dd>
<dt>Temp</dt>
<dd>
CPU mostly between 45 and 59 C°<br>
Mobo mostly between 50 and 60 C°
</dd>
<dt>Runtime</dt><dd>>3,5-4h</dd>
</dl>
<dl>
<dt>Type</dt><dd>Without Wifi+BT</dd>
<dt>Usage</dt><dd>~14,5-15,5 Watt</dd>
<dt>Temp</dt>
<dd>
CPU mostly between 45 and 59 C°<br>
Mobo mostly between 50 and 60 C°
</dd>
<dt>Runtime</dt><dd>~4,5-5h</dd>
</dl>
<h3>AC power consumption (without discrete graphics)</h3>
<dl>
<dt>Type</dt><dd>AC</dd>
<dt>Usage</dt><dd>> 20 Watt</dd>
<dt>Temp</dt>
<dd>
CPU mostly between 55 and 69 C°<br>
Mobo mostly between 55 and 69 C°
</dd>
<dt>Runtime</dt><dd>forever…</dd>
</dl>
<h3>AC power consumption (with discrete graphics)</h3>
<dl>
<dt>Type</dt><dd>AC/Battery</dd>
<dt>Usage</dt><dd>> 30 Watt</dd>
<dt>Temp</dt>
<dd>
CPU mostly between over 70 C°<br>
Mobo mostly over 70 C°<br>
Radeon mostly over 70 C°
</dd>
<dt>Runtime</dt><dd>forever…</dd>
</dl>
<br>
<h3 id="what-i-did">What I did</h3>
<p>Used google until my keyboard (almost) burned.</p>
<p>Using powertop and tweak what it demands. Ubuntu comes with pm-tools which allows own scripts to be executed when you go on battery.
For detailed configuration see <a href="https://bgrande.de/projects/linux-on-samsung-chronos7/#downloads">powersave</a>.</p>
<p><del>Turned off the discrete radoen by executing:</del> <br>
<del><code>echo IGD > /sys/kernel/debug/vgaswitcheroo/switch</code></del> <br>
<del><code>echo OFF > /sys/kernel/debug/vgaswitcheroo/switch</code></del></p>
<p>I have been wrong: the Chronos uses „PowerXpress“ to switch between cards.
So only acpi_call or the fglrx drivers can help with switching.<br>
But not vgaswitcheroo! Or just not loading the radeon driver.</p>
<p><del>Not sure if it helped: compiled and installed acpi_call module and deactivated the discrete radeon.</del></p>
<p>It may further help to switch off compiz. <del>Have not tried that yet.</del> It helps. But not enough…</p>
<p>Definitely a good thing: Install the recent ati/amd fglrx drivers.
A good description can be found at <a href="http://wiki.colar.net/ubuntu_12_04_on_samsung_series_7_chronos_laptop">http://wiki.colar.net/ubuntu_12_04_on_samsung_series_7_chronos_laptop</a>
and <a href="http://askubuntu.com/questions/205112/how-do-i-get-amd-intel-hybrid-graphics-drivers-to-work">http://askubuntu.com/questions/205112/how-do-i-get-amd-intel-hybrid-graphics-drivers-to-work</a> <br></p>
<p>If you get an Xorg-error like <code>fglrx-libGL.so.1.2 not found</code> with 64bit try linking <code>/usr/lib/fglrx</code> to <code>/usr/lib32/fglrx</code> <br>
Now you are able to use both cards (but you still have to restart the X server).<br>
<code>aticonfig --pxl</code> shows the card which is in use right now.</p>
<p>To switch it:<br>
<code>sudo aticonfig --px-dgpu # Activate discrete GPU</code> <br>
<code>sudo aticonfig --px-igpu # Activate integrated GPU</code></p>
<p>Starting with ubuntu 12.10 you might need Nick Andrik’s <a href="https://help.ubuntu.com/community/BinaryDriverHowto/ATI#WORKAROUND">Workaround</a>. For me it worked.</p>
<hr />
</div>
<div class="subsection">
<h2 id="installation-and-configuration"><a name="description"></a>Installation and Configuration</h2>
<p>No issues while ubuntu installation. Everything works fine there. Choosed ext4 + eCryptfs <code>/home</code>.
<br></p>
<h3 id="some-questions-and-possible-answers">Some Questions and possible answers</h3>
<dl>
<dt>Right click with touchpad (should work with kernel 3.2 and higher)</dt>
<dd>Two finger tap</dd>
<dt>Opening the case/rear cover (what worked for me):</dt>
<dd>get a perfectly matching screwdriver and open the rear cover screws (all of them including the one for memory access) – took me a while cause one screw did not want to move…open the cover by using your finger nails and the card reader dummy to hold spaces open once you lowered the cover a bit – be careful and patient, this is somewhat tricky…</dd>
<dt>Wifi issues</dt>
<dd>
<p><code>bcma/brcmsmac/mac80211</code> – no problems so far</p>
</dd>
<dt>Keyboard backlight</dt>
<dd>working fine for me<br>
but associated FN keys do not work</dd>
<dt>If your fan is almost always running</dt>
<dd>
<p>most definitly this is not caused by wrong cpu governing rather than the discrete radeon or internal intel graphics.
But even after disabling the dedicated radeon with vgaswitchero the laptop heats up very fast.
So there must be some other reason I did not figure out yet.
I did even try to shut it off with the acpi_call module (see <a href="https://bgrande.de/projects/linux-on-samsung-chronos7/#links">Sources & Links</a>).
I have to retest this but it seems to me that it helped.
Fan speed control is another topic I have not tried yet.</p>
</dd>
</dl>
<hr />
</div>
<br>
<div class="subsection">
<h2 id="scripts-and-downloads"><a name="downloads"></a>Scripts and Downloads</h2>
<dl>
<dt>
<p><a href="https://bgrande.de/projects/linux-on-samsung-chronos7/./96_powersave">Powersave</a></p>
</dt>
<dd>
<p>Modified PM powersave script from crunchbanglinux. Put it into <code>/etc/pm/power.d/</code> PM has its own hooks which are executed when going into battery mode.
By using the script with <code>96_</code> these tweaks will be overwritten.You can test it by executing the script like <code>sh 96_powersave true|false</code>.</dd></p>
<dd>
<p><strong>UPDATED August 26th 2012</strong></p>
</dd>
<dt>
<p><a href="https://bgrande.de/projects/linux-on-samsung-chronos7/./disable-wireless.txt">Disable/Enable wireless connections</a></p>
</dt>
<dd>This script provides functions to turn off wifi and bluetooth.
Either both or separately.
Still working on extending and improving this script</dd>
<dt>
<p><a href="https://bgrande.de/projects/linux-on-samsung-chronos7/./grub"><code>/etc/default/grub</code> configuration</a> </p>
</dt>
<dd>
<p>changes I have made to grub default config – <strong>UPDATED June 6th 2012</strong></p>
</dd>
<dt>
<p><a href="https://bgrande.de/projects/linux-on-samsung-chronos7/./xorg.conf.txt">Xorg configuration</a></p>
</dt>
<dd>xorg.conf adjustments for touchpad</dd>
</dl>
<hr />
</div>
<p>se tweaks will be overwritten.You can test it by executing the script like <code>sh 96_powersave true|false</code>. <strong>UPDATED August 26th 2012</strong> <a href="https://bgrande.de/projects/linux-on-samsung-chronos7/./disable-wireless.txt">Disable/Enable wireless connections</a> This script provides functions to turn off wifi and bluetooth. Either both or separately. Still working on extending and improving this script <a href="https://bgrande.de/projects/linux-on-samsung-chronos7/./grub"><code>/etc/default/grub</code> configuration</a> changes I have made to grub default config – <strong>UPDATED June 6th 2012</strong> xorg.conf adjustments for touchpad –</p>
<hr />
<br>
<div class="subsection">
<h2 id="sources-and-links"><a name="links"></a>Sources and Links</h2>
<p>No special order whatsoever...</p>
<dl>
<dt><a href="https://wiki.archlinux.org/index.php/Intel">https://wiki.archlinux.org/index.php/Intel</a></dt>
<dd>Arch Linux Intel Graphics description wiki article</dd>
<dt><a href="https://bbs.archlinux.org/viewtopic.php?pid=1030190">https://bbs.archlinux.org/viewtopic.php?pid=1030190</a></dt>
<dd>Discussion about heat problems with sandy bridge</dd>
<dt><a href="https://bbs.archlinux.org/viewtopic.php?id=132077">https://bbs.archlinux.org/viewtopic.php?id=132077</a></dt>
<dd>Another discussion about heat problems with sandy bridge</dd>
<dt><a href="https://github.com/mkottman/acpi_call/">https://github.com/mkottman/acpi_call/</a></dt>
<dd>mkottman’s acpi_call module github development page</dd>
<dt><a href="http://ubuntuforums.org/showthread.php?p=11160983">http://ubuntuforums.org/showthread.php?p=11160983</a></dt>
<dd>Discussion about battery life and power consumption with intel graphics</dd>
<dt><a href="http://ubuntuforums.org/showthread.php?t=1906363">http://ubuntuforums.org/showthread.php?t=1906363</a></dt>
<dd>Discussion about kernel 3.2 with Ubuntu Oneiric</dd>
<dt><a href="http://www.phoronix.com/scan.php?page=news_item&px=OTYwNA">http://www.phoronix.com/scan.php?page=news_item&px=OTYwNA</a></dt>
<dd>Phoronix article about linux power regression bug</dd>
<dt><a href="http://www.phoronix.com/scan.php?page=article&item=intel_i915_power&num=1">http://www.phoronix.com/scan.php?page=article&item=intel_i915_power&num=1</a></dt>
<dd>Phoronix article about power saving with intel i915</dd>
<dt><a href="http://www.phoronix.com/scan.php?page=article&item=linux_aspm_solution">http://www.phoronix.com/scan.php?page=article&item=linux_aspm_solution</a></dt>
<dd>More Phoronix on power regression, battery life and power consumption with pcie</dd>
<dt><a href="http://www.phoronix.com/scan.php?page=news_item&px=MTAwNjU">http://www.phoronix.com/scan.php?page=news_item&px=MTAwNjU</a></dt>
<dd>Phoronix article about i915 rc6’s ups and downs</dd>
<dt><a href="http://www.omgubuntu.co.uk/2011/11/linux-power-regression-overheating-problem-on-thinkpad-fixed/">http://www.omgubuntu.co.uk/2011/11/linux-power-regression-overheating-problem-on-thinkpad-fixed/</a></dt>
<dd>Blog article about overheating problem with thinkpad X300</dd>
<dt><a href="https://bugs.freedesktop.org/show_bug.cgi?id=41682">https://bugs.freedesktop.org/show_bug.cgi?id=41682</a></dt>
<dd>Bug Report – sometimes enabling rc6 leads to rendering issues</dd>
<dt><a href="http://www.codemonkeyjava.com/2012/01/14/getting-the-samsung-chronos-7-touchpad-working-in-ubuntu-11-10/">http://www.codemonkeyjava.com/2012/01/14/getting-the-samsung-chronos-7-touchpad-working-in-ubuntu-11-10/</a></dt>
<dd>Blog article about how to get the touchpad working – you need kernel 3.2 at least</dd>
<dt><a href="http://www.webupd8.org/2011/06/linux-kernel-power-issue-fix.html#comment-339773934">http://www.webupd8.org/2011/06/linux-kernel-power-issue-fix.html#comment-339773934</a></dt>
<dd>More on rc6 and tweaking – I might have to switch back to Ubuntu 10.10 to solve the temperature issue, though…</dd>
<dt><a href="http://www.williambrownstreet.net/blog/?p=387">http://www.williambrownstreet.net/blog/?p=387</a></dt>
<dd>Blog article and a lot of information about battery life improvement tweaks</dd>
<dt><a href="http://fridge.ubuntu.com/2011/04/21/the-power-user%E2%80%99s-guide-to-unity/">http://fridge.ubuntu.com/2011/04/21/the-power-user%E2%80%99s-guide-to-unity/</a></dt>
<dd>Unity information portal – might help with unity related issues</dd>
<dt><a href="https://help.ubuntu.com/community/HybridGraphics">https://help.ubuntu.com/community/HybridGraphics</a></dt>
<dd>Ubuntu help pages about hybrid graphics – vgaswitcheroo and other cool stuff</dd>
<dt><a href="http://superuser.com/questions/355836/compatibility-of-linux-with-samsung-series-7-chronos">http://superuser.com/questions/355836/compatibility-of-linux-with-samsung-series-7-chronos</a></dt>
<dd>Discussion about Linuxo on Chronos – initial reading hast not been that encouraging…</dd>
<dt><a href="http://crunchbanglinux.org/forums/topic/11954">http://crunchbanglinux.org/forums/topic/11954</a></dt>
<dd>Thread about power saving – Nice (quick and dirty) power saving script</dd>
<dt><a href="http://schibum.blogspot.de/2011/11/ubuntu-oneiric-on-samsung-series-7.html">http://schibum.blogspot.de/2011/11/ubuntu-oneiric-on-samsung-series-7.html</a></dt>
<dd>Blog article about Oneiric on Chronos – made me buy this laptop</dd>
<dt><a href="http://openideals.org/2012/04/15/tuning-ubuntu-on-samsung-series-7-laptop/">http://openideals.org/2012/04/15/tuning-ubuntu-on-samsung-series-7-laptop/</a></dt>
<dd>Another blog article about Linux on Chronos with good insights – tipped me to the following project</dd>
<dt><a href="https://launchpad.net/~voria/+archive/ppa">https://launchpad.net/~voria/+archive/ppa</a></dt>
<dd>Project discussing and including tools for samsung laptops.While the tools seem to be great I would not recommend to try phc-intel and samsung-backlight… Unless you like kernel panics at boot…Also the samsung_brightness tool sometimes does not let me increase the brightness anymore</dd>
<dt><a href="http://www.h-online.com/open/news/item/Linux-3-4-will-use-Intel-s-power-saving-RC6-GPU-technology-1503382.html">http://www.h-online.com/open/news/item/Linux-3-4-will-use-Intel-s-power-saving-RC6-GPU-technology-1503382.html</a></dt>
<dd>Article about RC6 in kernel 3.4</dd>
<dt><a href="http://brot.echorulez.de/doku.php?id=linux_powersave">http://brot.echorulez.de/doku.php?id=linux_powersave</a></dt>
<dd>More details on the i915 issue (German)</dd>
<dt><a href="http://kernel.ubuntu.com/~kernel-ppa/mainline/v3.4-precise/">http://kernel.ubuntu.com/~kernel-ppa/mainline/v3.4-precise/</a></dt>
<dd>Ubuntu 3.4 Mainline kernel for precise pangolin</dd>
<dt><a href="http://wiki.colar.net/ubuntu_12_04_on_samsung_series_7_chronos_laptop">http://wiki.colar.net/ubuntu_12_04_on_samsung_series_7_chronos_laptop</a></dt>
<dd>Another Linux and Chronos user. Might help when installing the ati fgrlx drivers</dd>
<dt><a href="http://ubuntuforums.org/showthread.php?t=1930450">http://ubuntuforums.org/showthread.php?t=1930450</a></dt>
<dd>Discussion and howto about using the chronos with the proprietary ati fglrx drivers</dd>
<dt><a href="https://help.ubuntu.com/community/BinaryDriverHowto/ATI#WORKAROUND">https://help.ubuntu.com/community/BinaryDriverHowto/ATI#WORKAROUND</a></dt>
<dd>Nick Andrik’s Workaround for 12.10</dd>
</dl>
<hr>
</div>
<h2 id="todo"><a name="todo"></a>Todo</h2>
<p><strong>Find a way to use the discrete radeon card (and automatically switch it on/off when (not) needed)</strong></p>
<p><strong>Switch off sound and measure power saving</strong></p>
<p><strong>Write an (indicator) applet to toggle bluetooth, wifi and sound (when on battery). Or automate it</strong></p>
Moving away from WordPress2016-11-23T22:11:08+00:002017-03-24T06:58:53+00:00https://bgrande.de/blog/moving-away-from-wordpress/<p>WordPress can be great for bigger multiuser projects, but it can be a pain optimizing for content and small assets (as in javascript, images and stylesheets) as well. So I had been planning moving to some alternative. Eventually I wrote it myself and here is the result. </p>
<span id="continue-reading"></span><br>
<p>In my opinion WordPress is quite overloaded (at least for most of my goals) so I have been looking for something to replace it for a while now.</p>
<p>And because it would be boring to just use something off the shelf and most likely overloaded and not as simple as I'd like, I wrote something myself.</p>
<p>Since it's not quite done yet, but you can already create content with it, I will add some details later.</p>
<p>But as you can probably see, it's based on static html pages and well optimized assets.</p>