<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://linda-jansson.com/feed.xml" rel="self" type="application/atom+xml"/><link href="https://linda-jansson.com/" rel="alternate" type="text/html"/><updated>2026-07-05T14:32:42+00:00</updated><id>https://linda-jansson.com/feed.xml</id><title type="html">linjan2 Sysadmin Notebook</title><subtitle>Technical notes, cheat sheets, and occasional web experiments</subtitle><author><name>Linda Jansson</name></author><entry><title type="html">Jekyll site setup</title><link href="https://linda-jansson.com/jekyll-site-setup/" rel="alternate" type="text/html" title="Jekyll site setup"/><published>2026-04-06T00:00:00+00:00</published><updated>2026-04-06T00:00:00+00:00</updated><id>https://linda-jansson.com/jekyll-site-setup</id><content type="html" xml:base="https://linda-jansson.com/jekyll-site-setup/"><![CDATA[<h1 id="jekyll-site-setup">Jekyll site setup</h1> <p>Setup a Jekyll static site generator with the <a href="https://github.com/jekyll/minima?tab=readme-ov-file">Minima theme for blog posts</a>.</p> <h2 id="jekyll-installation">Jekyll installation</h2> <p>See <a href="https://jekyllrb.com/docs/installation/other-linux/">Jekyll installation instructions for other Linux</a>.</p> <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># install ruby with dependencies</span>
<span class="nb">sudo </span>dnf <span class="nb">install</span> <span class="nt">--setopt</span> <span class="nv">install_weak_deps</span><span class="o">=</span>0 <span class="se">\</span>
  ruby ruby-devel openssl-devel redhat-rpm-config gcc-c++ @development-tools <span class="se">\</span>
  libffi-devel libyaml-devel
  <span class="c"># NOTE: these ruby packages install gems into `/usr/share/gems`</span>

<span class="c"># configure gem to install binaries into a proper user folder</span>
<span class="nb">echo</span> <span class="s1">'gem: --no-document --bindir ~/.local/share/gem/ruby/bin'</span> <span class="o">&gt;</span> ~/.gemrc
<span class="c"># add gem bin path to PATH</span>
<span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span>gem <span class="nb">env </span>user_gemhome<span class="si">)</span><span class="s2">/bin:</span><span class="k">${</span><span class="nv">PATH</span><span class="k">}</span><span class="s2">"</span>

<span class="c"># install jekyll and bundler</span>
gem <span class="nt">--version</span>
gem <span class="nb">install </span>jekyll bundler
</code></pre></div></div> <blockquote> <p>Persist the updated <code class="language-plaintext highlighter-rouge">PATH</code> variable for gem binaries in <code class="language-plaintext highlighter-rouge">~/.bashrc</code>.</p> <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cat</span> <span class="o">&gt;&gt;</span> ~/.bashrc &lt;&lt;<span class="s1">'
if command -v gem &amp;&gt;/dev/null
then
  GEM_HOME="$(gem env user_gemhome)"
  if ! [[ "$PATH" =~ "${GEM_HOME}/bin" ]]
  then
    PATH="${GEM_HOME}/bin:${PATH}"
  fi
fi
export PATH
'</span>
</code></pre></div> </div> </blockquote> <h2 id="configure-jekyll-site">Configure Jekyll site</h2> <p>Generate a site.</p> <ul> <li>Do not run <code class="language-plaintext highlighter-rouge">bundle install</code>/<code class="language-plaintext highlighter-rouge">bundle update</code> before configuring project local gem location to <code class="language-plaintext highlighter-rouge">./vendor/bundle</code>.</li> <li>By default a new Jekyll site uses Minima v2 theme gem, which we change to Minima v3 using the “remote theme” plugin.</li> <li>Replace all fixed gem <code class="language-plaintext highlighter-rouge">x.y.z</code> versions in Gemfile to <code class="language-plaintext highlighter-rouge">x.y</code> in order to get latest versions.</li> </ul> <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jekyll new <span class="nb">.</span> <span class="nt">--skip-bundle</span>
  <span class="c"># _config.yml</span>
  <span class="c"># _posts/</span>
  <span class="c"># index.markdown</span>
  <span class="c"># about.markdown</span>
  <span class="c"># 404.html</span>
  <span class="c"># Gemfile</span>
  <span class="c"># .gitignore</span>

<span class="c"># configure bundler to install gems into project folder</span>
bundle config <span class="nb">set</span> <span class="nt">--local</span> path <span class="s1">'vendor/bundle'</span>
</code></pre></div></div> <p>Clear the Gemfile in order to install latest gem versions. Manually remove directives related to Windows and JRuby.</p> <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># remove packages with fixed versions added by default</span>
bundle remove <span class="nt">--skip-install</span> minima
bundle remove <span class="nt">--skip-install</span> jekyll-feed
bundle remove <span class="nt">--skip-install</span> jekyll

bundle add <span class="nt">--skip-install</span> jekyll

bundle add <span class="nt">--skip-install</span> <span class="se">\</span>
  <span class="nt">--group</span> jekyll_plugins <span class="se">\</span>
  jekyll-remote-theme <span class="se">\</span>
  jekyll-feed <span class="se">\</span>
  jekyll-seo-tag <span class="se">\</span>
  jekyll-minifier
</code></pre></div></div> <p>Remove all patch versions in the <code class="language-plaintext highlighter-rouge">Gemfile</code>. It should look like this:</p> <pre><code class="language-gemfile">source "https://rubygems.org"

gem "jekyll", "~&gt; 4.4"

gem "jekyll-remote-theme", "~&gt; 0.4", group: :jekyll_plugins
gem "jekyll-feed", "~&gt; 0.17", group: :jekyll_plugins
gem "jekyll-seo-tag", "~&gt; 2.8", group: :jekyll_plugins
gem "jekyll-minifier", "~&gt; 0.2", group: :jekyll_plugins
</code></pre> <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># remove Gemfile.lock in order to update to latest gem package versions</span>
<span class="nb">rm </span>Gemfile.lock
<span class="c"># update gem versions</span>
bundle update <span class="nt">--all</span>
bundle <span class="nb">install</span>
<span class="c"># clean up unused gems</span>
bundle clean  <span class="c"># project local</span>
gem clean     <span class="c"># user installed gems</span>
</code></pre></div></div> <p>Edit <code class="language-plaintext highlighter-rouge">_config.yml</code> to configure the site. The variables in <code class="language-plaintext highlighter-rouge">_config.yml</code> will be accessible in the templates via <code class="language-plaintext highlighter-rouge">{{ site.myvariable }}</code> (e.g. <code class="language-plaintext highlighter-rouge">{{ site.author.name }}</code>).</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># _config.yml</span>
<span class="na">title</span><span class="pi">:</span> <span class="s">My Jekyll site</span>
<span class="na">author</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">linjan2</span>
  <span class="na">email</span><span class="pi">:</span>
<span class="na">description</span><span class="pi">:</span> <span class="pi">&gt;-</span>
  <span class="s">Site description that will appear in document head meta and in feed.xml</span>
  <span class="s">site description.</span>
<span class="na">url</span><span class="pi">:</span> <span class="s">https://linda-jansson.com</span>
<span class="na">baseurl</span><span class="pi">:</span> <span class="s2">"</span><span class="s">"</span> <span class="c1"># the subpath of your site, e.g. /blog, if served from subpath</span>

<span class="c1"># Replace "theme: minima" with remote_theme</span>
<span class="na">remote_theme</span><span class="pi">:</span> <span class="s">jekyll/minima@bf9ef98</span> <span class="c1"># static ref for Minima v3</span>
  <span class="c1"># See docs at: https://github.com/jekyll/minima/tree/master</span>

<span class="na">plugins</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">jekyll-remote-theme</span>
  <span class="pi">-</span> <span class="s">jekyll-feed</span>
  <span class="pi">-</span> <span class="s">jekyll-minifier</span>
  <span class="pi">-</span> <span class="s">jekyll-seo-tag</span>

<span class="c1"># Exclude files from processing (in addition to the default jekyll excludes)</span>
<span class="na">exclude</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">tmp</span>

<span class="na">minima</span><span class="pi">:</span>
  <span class="na">skin</span><span class="pi">:</span> <span class="s">auto</span>

<span class="na">jekyll-minifier</span><span class="pi">:</span>
  <span class="na">exclude</span><span class="pi">:</span> <span class="pi">[]</span>
</code></pre></div></div> <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># test by serving on http://localhost:4000</span>
bundle <span class="nb">exec </span>jekyll serve

<span class="c"># build the generated website into _site/</span>
<span class="nb">export </span><span class="nv">JEKYLL_ENV</span><span class="o">=</span>production <span class="c"># required to activate things like jekyll-minifier</span>
bundle <span class="nb">exec </span>jekyll build
</code></pre></div></div> <p>Commit the site files to Git repository.</p> <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">echo</span> <span class="s1">'tmp/'</span> <span class="o">&gt;&gt;</span> .gitignore
<span class="nb">echo</span> <span class="s1">'.bundle/'</span> <span class="o">&gt;&gt;</span> .gitignore
git init <span class="nb">.</span> <span class="o">&amp;&amp;</span> git add <span class="nb">.</span> <span class="o">&amp;&amp;</span> git commit <span class="s1">'initial commit'</span>
</code></pre></div></div> <h2 id="add-more-custom-site-files">Add more custom site files</h2> <p>A Jekyll theme utilizes a folder structure to define the posts, layouts, and includes. Make custom additions by adding files to local project folder. Files in local project folder override those pre-defined in the theme.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./
├── assets/
│   ├── about.svg
│   ├── css/
│   │   └── custom.css
│   ├── index.svg
│   └── jekyll-example/
│       ├── index.js
│       └── style.css
├── _config.yml
├── _drafts/
├── favicon.ico
├── Gemfile
├── Gemfile.lock
├── _includes/
│   ├── custom-head.html
│   └── sub-footer.html
├── index.md
├── pages/
│   ├── 404.md
│   ├── about.md
│   └── categories.md
└── _posts/
    ├── 2026-04-08-jekyll-example-html-post.html
    └── 2026-04-06-jekyll-example-post.md
</code></pre></div></div> <p>Update the <code class="language-plaintext highlighter-rouge">_config.yml</code> to add more configurations.</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># ...</span>

<span class="na">minima</span><span class="pi">:</span>
  <span class="c1"># ...</span>
  <span class="na">nav_pages</span><span class="pi">:</span> <span class="c1"># Update the navigation pane with specific site pages</span>
    <span class="pi">-</span> <span class="s">pages/categories.md</span>
    <span class="pi">-</span> <span class="s">pages/about.md</span>
  <span class="na">date_format</span><span class="pi">:</span> <span class="s2">"</span><span class="s">%b</span><span class="nv"> </span><span class="s">%-d,</span><span class="nv"> </span><span class="s">%Y"</span>
  <span class="na">social_links</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">title</span><span class="pi">:</span> <span class="s">GitHub</span>
      <span class="na">icon</span><span class="pi">:</span> <span class="s">github</span>
      <span class="na">url</span><span class="pi">:</span> <span class="s">https://github.com/linjan2</span>

<span class="na">disqus</span><span class="pi">:</span>
  <span class="na">shortname</span><span class="pi">:</span> <span class="s">linda-jansson-com</span>

<span class="c1"># set defaults for front matter variables</span>
<span class="na">defaults</span><span class="pi">:</span>
  <span class="c1"># for all files</span>
  <span class="pi">-</span> <span class="na">scope</span><span class="pi">:</span>
      <span class="na">path</span><span class="pi">:</span> <span class="s1">'</span><span class="s">'</span> <span class="c1"># matches all</span>
    <span class="na">values</span><span class="pi">:</span>
      <span class="na">author</span><span class="pi">:</span> <span class="s">Linda Jansson</span>
  <span class="c1"># for all files under _posts/</span>
  <span class="pi">-</span> <span class="na">scope</span><span class="pi">:</span>
      <span class="na">path</span><span class="pi">:</span> <span class="s">_posts</span>
      <span class="na">type</span><span class="pi">:</span> <span class="s">posts</span>
    <span class="na">values</span><span class="pi">:</span>
      <span class="na">layout</span><span class="pi">:</span> <span class="s">post</span>
      <span class="na">permalink</span><span class="pi">:</span> <span class="s">/:title/</span>
  <span class="c1"># for all files under _pages/</span>
  <span class="pi">-</span> <span class="na">scope</span><span class="pi">:</span>
      <span class="na">path</span><span class="pi">:</span> <span class="s">pages</span>
    <span class="na">values</span><span class="pi">:</span>
      <span class="na">layout</span><span class="pi">:</span> <span class="s">page</span>
      <span class="na">comments</span><span class="pi">:</span> <span class="kc">false</span> <span class="c1"># don't display disqus</span>
</code></pre></div></div> <p>Create a custom “404 Not found” page.</p> <div class="language-md highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gh">&lt;!-- pages/404.md --&gt;
---
</span>layout: page
title: 404 - Page Not Found
<span class="gh">permalink: /404.html
---
</span>
Sorry, we couldn't find the page you're looking for.

<span class="p">[</span><span class="nv">← Back to Home</span><span class="p">](</span><span class="sx">/</span><span class="p">)</span>
</code></pre></div></div> <p>Create an “about” page. The liquid templating language is used to insert a SVG file fron <code class="language-plaintext highlighter-rouge">static_files</code> variables, which holds all “static” site files that don’t have a front matter.</p> <div class="language-md highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gh">&lt;!-- pages/about.md --&gt;
---
</span>layout: page
title: About
<span class="gh">permalink: /about/
---
</span>

{% assign file = site.static_files | where: "name", "about.svg" | first %}
&lt;img
  src="{{ file.path }}"
  title="Flower image"
  alt="Image of a pink flower"
  height="100px"
  style="display: block; margin-left: auto; margin-right: 0;"
/&gt;

</code></pre></div></div> <p>Create a page to display all post categories with links.</p> <div class="language-md highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gh">&lt;!-- pages/categories.md --&gt;
---
</span>layout: page
permalink: /categories/
<span class="gh">title: Categories
---
</span>

<span class="nt">&lt;div</span> <span class="na">id=</span><span class="s">"archives"</span><span class="nt">&gt;</span>
{% assign sorted_categories = site.categories | sort %}
{% for category in sorted_categories %}
  <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"archive-group"</span><span class="nt">&gt;</span>
    {% capture category_name %}{{ category | first }}{% endcapture %}
    <span class="nt">&lt;div</span> <span class="na">id=</span><span class="s">"{{ category_name | slugize }}"</span><span class="nt">&gt;&lt;/div&gt;</span>
    <span class="nt">&lt;h3</span> <span class="na">class=</span><span class="s">"category-head"</span><span class="nt">&gt;</span>{{ category_name }}<span class="nt">&lt;/h3&gt;</span>
    <span class="nt">&lt;a</span> <span class="na">name=</span><span class="s">"{{ category_name | slugize }}"</span><span class="nt">&gt;&lt;/a&gt;</span>
    {% for post in site.categories[category_name] %}
    <span class="nt">&lt;article</span> <span class="na">class=</span><span class="s">"archive-item"</span><span class="nt">&gt;</span>
      <span class="nt">&lt;h4&gt;&lt;a</span> <span class="na">href=</span><span class="s">"{{ site.baseurl }}{{ post.url }}"</span><span class="nt">&gt;</span>{{ post.title }}<span class="nt">&lt;/a&gt;&lt;/h4&gt;</span>
    <span class="nt">&lt;/article&gt;</span>
    {% endfor %}
  <span class="nt">&lt;/div&gt;</span>
{% endfor %}
<span class="nt">&lt;/div&gt;</span>

</code></pre></div></div> <p>Override the Minima theme’s custom head <code class="language-plaintext highlighter-rouge">_includes/custom-head.html</code> to include CSS assets (one common <code class="language-plaintext highlighter-rouge">custom.css</code> and then a loop to include from a variable named <code class="language-plaintext highlighter-rouge">css</code> that can be set in a file’s front matter).</p> <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- _includes/custom-head.html --&gt;</span>

<span class="nt">&lt;link</span> <span class="na">rel=</span><span class="s">"stylesheet"</span> <span class="na">href=</span><span class="s">"/assets/css/custom.css"</span><span class="nt">&gt;</span>
{%- for css in page.css -%}
  <span class="nt">&lt;link</span> <span class="na">rel=</span><span class="s">"stylesheet"</span> <span class="na">href=</span><span class="s">"{{ css | relative_url }}"</span><span class="nt">&gt;</span>
{%- endfor -%}

</code></pre></div></div> <p>Override the Minima theme’s custom sub-footer (i.e. before end of body closing tag) <code class="language-plaintext highlighter-rouge">_includes/sub-footer.html</code> to include JavaScript assets (from variable set in front matter).</p> <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- _includes/sub-footer.html --&gt;</span>

{%- for js in page.js -%}
  <span class="nt">&lt;script </span><span class="na">src=</span><span class="s">"{{ js | relative_url }}"</span><span class="nt">&gt;&lt;/script&gt;</span>
{%- endfor -%}

</code></pre></div></div> <h3 id="create-posts">Create posts</h3> <p>Jekyll posts must be named <code class="language-plaintext highlighter-rouge">YEAR-MONTH-DAY-title.MARKUP</code>. Create the post files under <code class="language-plaintext highlighter-rouge">_posts/</code>. Drafts can be created under <code class="language-plaintext highlighter-rouge">_drafts</code>.</p> <p>Create a markdown post:</p> <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cat</span> <span class="o">&gt;</span> <span class="s2">"_posts/</span><span class="si">$(</span><span class="nb">date</span> +%F<span class="si">)</span><span class="s2">-jekyll-example-post.md"</span> <span class="o">&lt;&lt;&lt;</span><span class="s1">'---
title: Jekyll example post
categories: cheatsheet
---

# Jekyll example post

This is an example post.
'</span>
</code></pre></div></div> <p>Create an HTML post that avoids template interpretation. Add variables <code class="language-plaintext highlighter-rouge">js</code> and <code class="language-plaintext highlighter-rouge">css</code> in front matter to make the head and sub-footer in <code class="language-plaintext highlighter-rouge">_includes/</code> insert them as CSS/JS includes.</p> <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cat</span> <span class="o">&gt;</span> <span class="s2">"_posts/</span><span class="si">$(</span><span class="nb">date</span> +%F<span class="si">)</span><span class="s2">-jekyll-example-html-post.html"</span> <span class="o">&lt;&lt;&lt;</span><span class="s1">'---
title: Jekyll example HTML post
js:
  - https://unpkg.com/petite-vue@0.4.1/dist/petite-vue.iife.js
  - /assets/jekyll-example/index.js
css:
  - /assets/jekyll-example/style.css
categories: example
---

&lt;!-- avoid excerpt separator \n\n --&gt;

{% raw %}

&lt;h1&gt;Jekyll example HTML post&lt;/h1&gt;

{% endraw %}
'</span>
</code></pre></div></div>]]></content><author><name>Linda Jansson</name></author><category term="cheatsheet"/><summary type="html"><![CDATA[Jekyll site setup]]></summary></entry><entry><title type="html">GLSL editor</title><link href="https://linda-jansson.com/glsl-editor/" rel="alternate" type="text/html" title="GLSL editor"/><published>2025-11-14T00:00:00+00:00</published><updated>2025-11-14T00:00:00+00:00</updated><id>https://linda-jansson.com/glsl-editor</id><content type="html" xml:base="https://linda-jansson.com/glsl-editor/"><![CDATA[ <article id="app"> <div class="debug hidden"> <div>FPS: {{ state.fps }}</div> <div>Stopped: {{ state.stopped }}</div> </div> <div id="canvas-container"> <canvas id="canvas" width="960" height="540"> Browser doesn't support HTML5 canvas. </canvas> </div> <div> <div class="flex flex-row"> <button @click="toggle">{{ state.stopped ? "Start" : "Stop" }} render</button> <button @click="run">Compile shaders</button> </div> </div> <p>Vertex shader:</p> <pre class="editor" data-lang="cpp">
#version 100
uniform float u_rotate;
uniform vec4 u_color;
uniform vec2 u_aspect;
uniform vec2 u_mouse;
uniform float u_time;

attribute vec2 position;
attribute vec2 texel;

varying vec4 color;
varying vec2 st;
//varying float time;

void main() {
  gl_PointSize = 5.0;

  vec2 p = position * u_aspect * 2.0 - 1.0; // aspect is inverted; x,y on [-1, 1]

  vec2 u = vec2(1.0, u_rotate);
  vec2 z = vec2(1.0-u_rotate*u_rotate, 2.0*u_rotate) / dot(u,u);
  // complex multiplication z*p (mat2 is column-major)
  // p = vec2(z.x*position.x - z.y*position.y, z.x*position.y + z.y*position.x);
  mat2 rot = mat2(z.x, z.y, -z.y, z.x);

  gl_Position = vec4(rot*p, -1.0, 1.0);

  color = u_color;
  st = vec2(texel.x, 1.0-texel.y); // s & t on [0,1]
  //time = u_time;
}
</pre> <p>Fragment shader:</p> <pre class="editor" data-lang="cpp">
#version 100
precision mediump float;
uniform sampler2D u_sampler;
uniform vec2 u_dimensions;
varying vec4 color;
varying vec2 st;
// varying vec2 time;

void main() {
  vec2 p = gl_FragCoord.xy * u_dimensions;
  gl_FragColor = vec4(p, 1.0, 1.0);

  // gl_FragColor = color;
  // gl_FragColor = texture2D(u_sampler, st);
}
</pre> <template id="editor-template"> <div ref="codemirror"></div> <button @click="reset">Reset</button> </template> </article>]]></content><author><name>Linda Jansson</name></author><category term="⚙️webapps"/><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Guess the word</title><link href="https://linda-jansson.com/guess-the-word/" rel="alternate" type="text/html" title="Guess the word"/><published>2025-11-14T00:00:00+00:00</published><updated>2025-11-14T00:00:00+00:00</updated><id>https://linda-jansson.com/guess-the-word</id><content type="html" xml:base="https://linda-jansson.com/guess-the-word/"><![CDATA[ <article id="app" v-scope @vue:mounted="reload()"> <figure> <figcaption>Guess the word! Kind of like Wordle. Correctly placed letters are shown in green and bold. Letters that occur in the word, but are not correctly placed are shown in purple and italic. The guess must fill all letters of the word, but doesn't have to be a valid dictionary word. Guess as many times as you want.</figcaption> <select v-model="type" @input="select" name="language"> <option value="words">English</option> <option value="ord">Swedish</option> </select> <button @click="reload()">New game</button> <div class="words flex flex-column" :class="{solved: solved}"> <div v-for="(r, i) in letters" :key="i" class="flex flex-row letters-row"> <span v-for="(c, j) in r" :key="j" class="letter" :class="c.c">{{c.l}}</span> </div> <div class="flex flex-row letters-row" v-if="!solved"> <span v-for="n in word.length" :key="n" :class="{unfilled: !input[n-1]}" class="letter">{{input[n-1] || '.'}}</span> </div> <div id="guess-input" class="flex flex-row content"> <div class="flex flex-row"><input :maxlength="word.length" :disabled="solved||word===''" placeholder="enter a word here" type="text" v-model="input" @keyup.enter="add(input)"/></div> <div class="flex flex-row"><button @click="add(input)" :disabled="solved || word==='' || input.length!==word.length" name="guess">Guess</button></div> </div> </div> <div v-if="solved && definition!==null"> <blockquote :cite="definition.wordnikUrl"> <p> <a :href="definition.wordnikUrl"><dfn>{{definition.word}}</dfn></a> <br>{{definition.text}} <br>—<a :href="definition.attributionUrl"><cite>{{definition.attributionText}}</cite></a> </p> </blockquote> </div> </figure> </article>]]></content><author><name>Linda Jansson</name></author><category term="⚙️webapps"/><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Spin the wheel</title><link href="https://linda-jansson.com/spin-the-wheel/" rel="alternate" type="text/html" title="Spin the wheel"/><published>2025-11-14T00:00:00+00:00</published><updated>2025-11-14T00:00:00+00:00</updated><id>https://linda-jansson.com/spin-the-wheel</id><content type="html" xml:base="https://linda-jansson.com/spin-the-wheel/"><![CDATA[ <svg width="0" height="0" style="position:absolute" aria-hidden="true"> <defs> <radialGradient id="shadowGradient" r="0.8" cx="0.75" cy="0.75" fx="0.25" fy="0.25"> <stop offset="0%" stop-color="#FFF"/> <stop offset="100%" stop-color="#000"/> </radialGradient> </defs> </svg> <main id="main"> <article id="app" v-scope> <div id="input" class="flex"> <div><h2>Select items for the wheel</h2></div> <div><ul> <li v-for="(item, i) in items" :key="i"> <input type="text" v-model="item.text" @blur="if (item.text==='') remove(i)" @keyup.enter="if (item.text==='') remove(i)"/> </li> <li> <input id="add" placeholder="add an item..." type="text" v-model="input" @blur="if (input!=='') add(input)" @keyup.enter="if (input!=='') add(input)"/> </li> </ul></div> </div> <button :disabled="playing || items.length==0" @click="playButton()">Spin!</button> <div id="figure"> <figure> <figcaption>Pressing the button spins the wheel.</figcaption> <svg id="spinwheel" width="100%" xmlns="http://www.w3.org/2000/svg" viewBox="-1000 -1000 2000 2000"> <circle cx="0" cy="0" r="1000.0" :fill="background" stroke="none"/> <g :class="{ playing: playing }" @animationend="animationend()"> <g :transform="`matrix(${Zs.x},${Zs.y},${-Zs.y},${Zs.x},0,0)`"> <g v-for="(item, i) in items" :key="i" stroke="none" :transform="`matrix(${item.x},${item.y},${-item.y},${item.x},0,0)`"> <path :d="`M0,0 h1000 A1000,1000 0 0,1 ${1000*R[1].x},${1000*-R[1].y} z`" :fill="item.c"/> <text x="800" y="0" :transform="`matrix(${zhalf.x},${-zhalf.y},${zhalf.y},${zhalf.x},0,0)`" font-family="sans-serif" font-weight="800" :font-size="fontsize(item.text)" dominant-baseline="central" alignment-baseline="middle" text-anchor="end" fill="#FFF" stroke="#000" stroke-width="10" paint-order="stroke">{{item.text}}</text> </g> </g> </g> <circle cx="0" cy="0" r="880.0" fill="url(#shadowGradient)" stroke="url(#shadowGradient)" opacity="0.3" stroke-width="80"/> <path d="M960,-80 l -100,80 l 100,80 q 50 -80 0 -160 z" fill="#999" stroke="#3e3c38" stroke-width="15" stroke-linejoin="round" stroke-linecap="round"/> <line x1="960" y1="0" x2="1000" y2="0" stroke="#3e3c38" stroke-width="50" stroke-linecap="round"/> <circle cx="0" cy="0" r="50" fill="#999" stroke="#3e3c38" stroke-width="20"/> </svg> </figure> </div> <button :disabled="playing || items.length==0" @click="playButton()">Spin!</button> <div id="output" class="flex"> <h2>Results</h2> <ul> <li v-if="items2.length===0">...</li> <li v-for="(item, i) in items2" :key="i">{{items2.length - i}}. {{item}}</li> </ul> </div> </article> </main>]]></content><author><name>Linda Jansson</name></author><category term="⚙️webapps"/><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Camera tethering on Linux</title><link href="https://linda-jansson.com/camera-tethering-on-linux/" rel="alternate" type="text/html" title="Camera tethering on Linux"/><published>2024-11-10T00:00:00+00:00</published><updated>2024-11-10T00:00:00+00:00</updated><id>https://linda-jansson.com/camera-tethering-on-linux</id><content type="html" xml:base="https://linda-jansson.com/camera-tethering-on-linux/"><![CDATA[<h1 id="camera-tethering-on-linux">Camera tethering on Linux</h1> <p>Install <code class="language-plaintext highlighter-rouge">gphoto2</code>.</p> <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>dnf <span class="nb">install </span>gphoto2
man gphoto2

<span class="c"># show settings</span>
<span class="nb">cat</span> ~/.gphoto/settings

<span class="c"># various options</span>
gphoto2 <span class="nt">--list-config</span>
gphoto2 <span class="nt">--get-config</span> shutterspeed
gphoto2 <span class="nt">--get-config</span> capturetarget
<span class="c"># only use camera RAM for storing images (not SD card)</span>
gphoto2 <span class="nt">--set-config</span> <span class="nv">capturetarget</span><span class="o">=</span>0

<span class="c"># list the auto-detected cameras</span>
gphoto2 <span class="nt">--auto-detect</span>
gphoto2 <span class="nt">--summary</span>
gphoto2 <span class="nt">--list-files</span>
gphoto2 <span class="nt">--get-all-files</span>
</code></pre></div></div> <p>Create the script <code class="language-plaintext highlighter-rouge">gphoto-hook.sh</code> as below. It’s the hook that is run when a photo is taken.</p> <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/sh</span>
<span class="k">case</span> <span class="s2">"</span><span class="k">${</span><span class="nv">ACTION</span><span class="k">}</span><span class="s2">"</span> <span class="k">in
  </span>start<span class="p">)</span>
  <span class="p">;;</span>
  download<span class="p">)</span>
    <span class="nb">echo</span> <span class="s2">"Downloaded image </span><span class="k">${</span><span class="nv">PWD</span><span class="k">}</span><span class="s2">/</span><span class="k">${</span><span class="nv">ACTION</span><span class="k">}</span><span class="s2">"</span>
  <span class="p">;;</span>
<span class="k">esac</span>
</code></pre></div></div> <p>Start tethering.</p> <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># tether and capture camera shots</span>
gphoto2 <span class="nt">--capture-tethered</span> <span class="nt">--hook-script</span><span class="o">=</span>./gphoto-hook.sh
  <span class="c"># if multiple devices are detected use e.g. --camera='Sony Alpha-A6000'</span>
</code></pre></div></div> <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># capture and save on SD card</span>
gphoto2 <span class="nt">--capture-image</span>

<span class="c"># capture and save on SD card, download to computer, then delete from camera</span>
gphoto2 <span class="nt">--capture-image-and-download</span> <span class="nt">--filename</span> %Y%m%d%H%M%S.arw
</code></pre></div></div>]]></content><author><name>Linda Jansson</name></author><category term="linux"/><summary type="html"><![CDATA[Camera tethering on Linux]]></summary></entry><entry><title type="html">Create Android Virtual Devices (AVD)</title><link href="https://linda-jansson.com/create-android-virtual-devices-(avd)/" rel="alternate" type="text/html" title="Create Android Virtual Devices (AVD)"/><published>2024-11-02T00:00:00+00:00</published><updated>2024-11-02T00:00:00+00:00</updated><id>https://linda-jansson.com/create-android-virtual-devices-(avd)</id><content type="html" xml:base="https://linda-jansson.com/create-android-virtual-devices-(avd)/"><![CDATA[<h1 id="create-android-virtual-devices-avd">Create Android Virtual Devices (AVD)</h1> <p>The AVD Manager CLI tool <a href="https://developer.android.com/tools/avdmanager"><code class="language-plaintext highlighter-rouge">avdmanager</code></a> is included in the Android SDK command-line tools package.</p> <p>The SDK manager tool <a href="https://developer.android.com/tools/sdkmanager"><code class="language-plaintext highlighter-rouge">sdkmanager</code></a> installs and updates packages. First download the latest version of “Command line tools only” .zip file from <a href="https://developer.android.com/studio#command-line-tools-only">https://developer.android.com/studio#command-line-tools-only</a>. Then use <code class="language-plaintext highlighter-rouge">sdkmanager</code> to reinstall <code class="language-plaintext highlighter-rouge">cmdline-tools</code> as a package in an appropriate location.</p> <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir</span> <span class="nt">-p</span> /path/to/sdk/

unzip commandlinetools-linux-11076708_latest.zip
cmdline-tools/bin/sdkmanager <span class="nt">--sdk_root</span><span class="o">=</span>/path/to/sdk <span class="s1">'cmdline-tools;latest'</span>

<span class="nb">export</span> <span class="s2">"PATH=</span><span class="k">${</span><span class="nv">PATH</span><span class="k">}</span><span class="s2">:/path/to/sdk/cmdline-tools/latest/bin"</span>

sdkmanager <span class="nt">--list_installed</span>
</code></pre></div></div> <p>Use <code class="language-plaintext highlighter-rouge">avdmanager</code> to create virtual device. A system image and the emulator must be installed first.</p> <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sdkmanager <span class="nt">--list</span> | <span class="nb">grep </span>system-images
<span class="c"># install the emulator, a system image (with play store),</span>
<span class="c"># an SDK platform, and the platform tools</span>
sdkmanager emulator <span class="se">\</span>
  <span class="s1">'system-images;android-35;google_apis_playstore;x86_64'</span> <span class="se">\</span>
  <span class="s1">'platforms;android-35'</span> <span class="se">\</span>
  platform-tools

<span class="c"># list device definitions (e.g. pixel_8)</span>
avdmanager list device <span class="nt">--compact</span>

avdmanager create avd <span class="nt">--name</span> pixel8_api35 <span class="se">\</span>
  <span class="nt">--package</span> <span class="s1">'system-images;android-35;google_apis_playstore;x86_64'</span> <span class="se">\</span>
  <span class="nt">--device</span> pixel_8
avdmanager create avd <span class="nt">--name</span> desktop_medium_api35 <span class="se">\</span>
<span class="nt">--package</span> <span class="s1">'system-images;android-35;google_apis_playstore;x86_64'</span> <span class="se">\</span>
<span class="nt">--device</span> desktop_medium

<span class="c"># list available Andoid virtual devices</span>
avdmanager list avd

<span class="c"># edit device settings</span>
vim ~/.android/avd/desktop_medium_api35.avd/config.ini
  <span class="c"># PlayStore.enabled = yes</span>
</code></pre></div></div> <p>Start the Android device with <a href="https://developer.android.com/studio/run/emulator-commandline"><code class="language-plaintext highlighter-rouge">emulator</code></a>.</p> <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">export </span><span class="nv">ANDROID_SDK_ROOT</span><span class="o">=</span>/path/to/sdk
<span class="nb">export</span> <span class="s2">"PATH=</span><span class="k">${</span><span class="nv">PATH</span><span class="k">}</span><span class="s2">:</span><span class="k">${</span><span class="nv">ANDROID_SDK_ROOT</span><span class="k">}</span><span class="s2">/emulator"</span>

<span class="c"># show help</span>
emulator <span class="nt">-help-all</span>
emulator <span class="nt">-help-environment</span> <span class="c"># help on environment variables</span>

<span class="c"># list all created AVDs</span>
emulator <span class="nt">-list-avds</span>
  <span class="c"># desktop_medium_api35</span>
  <span class="c"># pixel8_api35</span>

<span class="c"># start the emulator (close its window to exit)</span>
emulator <span class="nt">-avd</span> desktop_medium_api35

<span class="c"># </span>
emulator <span class="nt">-avd</span> desktop_medium_api35 <span class="nt">-wipe-data</span>
</code></pre></div></div> <p>Rename a virtual device.</p> <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>avdmanager move avd <span class="nt">--name</span> desktop_medium_api35 <span class="nt">--rename</span> desktop_medium_api35_old
</code></pre></div></div> <p>Delete a virtual device.</p> <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>avdmanager delete avd <span class="nt">--name</span> pixel8_api35
</code></pre></div></div>]]></content><author><name>Linda Jansson</name></author><category term="uncategorized"/><summary type="html"><![CDATA[Create Android Virtual Devices (AVD)]]></summary></entry><entry><title type="html">GitLab pipelines in monorepo</title><link href="https://linda-jansson.com/gitlab-pipelines-in-monorepo/" rel="alternate" type="text/html" title="GitLab pipelines in monorepo"/><published>2024-10-19T00:00:00+00:00</published><updated>2024-10-19T00:00:00+00:00</updated><id>https://linda-jansson.com/gitlab-pipelines-in-monorepo</id><content type="html" xml:base="https://linda-jansson.com/gitlab-pipelines-in-monorepo/"><![CDATA[<h1 id="gitlab-pipelines-in-monorepo">GitLab pipelines in monorepo</h1> <p>In a monorepo as shown below, each sub-project folder contains its own pipeline file <code class="language-plaintext highlighter-rouge">.gitlab-ci.yml</code>. The top-level <code class="language-plaintext highlighter-rouge">.gitlab-ci.yml</code> conditionally includes a sub-folder <code class="language-plaintext highlighter-rouge">.gitlab-ci.yml</code> if that sub-folder’s files changed.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.
├── .gitlab-ci.yml
├── subproject1/
│   ├── .gitlab-ci.yml
│   ├── Dockerfile
│   └── src/
└── subproject2/
    ├── .gitlab-ci.yml
    ├── Dockerfile
    └── src/
</code></pre></div></div> <p>The top-level <code class="language-plaintext highlighter-rouge">.gitlab-ci.yml</code> which uses <code class="language-plaintext highlighter-rouge">include</code>. See documentation for <code class="language-plaintext highlighter-rouge">include</code> at <a href="https://docs.gitlab.com/ee/ci/yaml/includes.html">Use CI/CD configuration from other files</a>.</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># .gitlab-ci.yml</span>
<span class="na">include</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">local</span><span class="pi">:</span> <span class="s">subproject1/.gitlab-ci.yml</span>
    <span class="na">rules</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">changes</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="s">subproject1/src/**/*</span>
          <span class="pi">-</span> <span class="s">subproject1/Dockerfile</span>

  <span class="pi">-</span> <span class="na">local</span><span class="pi">:</span> <span class="s">subproject2/.gitlab-ci.yml</span>
    <span class="na">rules</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">changes</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="s">subproject2/src/**/*</span>
          <span class="pi">-</span> <span class="s">subproject2/Dockerfile</span>

<span class="c1"># avoid empty pipeline errors when no includes are run</span>
<span class="na">dummy</span><span class="pi">:</span>
  <span class="na">rules</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">when</span><span class="pi">:</span> <span class="s">never</span>
  <span class="na">script</span><span class="pi">:</span> <span class="s1">'</span><span class="s">:'</span>
</code></pre></div></div> <p>The subproject pipelines below are written independently of each other. Their working directory is the repository root, so they use the files of their respective project folder.</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># subproject1/.gitlab-ci.yml</span>
<span class="na">subproject1</span><span class="pi">:</span>
  <span class="na">stage</span><span class="pi">:</span> <span class="s">build</span>
  <span class="na">image</span><span class="pi">:</span> <span class="s">quay.io/buildah/stable:latest</span>
  <span class="na">variables</span><span class="pi">:</span>
    <span class="na">PROJECT_DIR</span><span class="pi">:</span> <span class="s">subproject1</span>
    <span class="na">STORAGE_DRIVER</span><span class="pi">:</span> <span class="s">vfs</span>
    <span class="na">BUILDAH_FORMAT</span><span class="pi">:</span> <span class="s">oci</span>
    <span class="na">IMAGE_NAME</span><span class="pi">:</span> <span class="s">${CI_REGISTRY_IMAGE}/${CI_JOB_NAME_SLUG}:${CI_COMMIT_REF_SLUG}</span>
  <span class="na">script</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="pi">|</span>
      <span class="s">buildah login \</span>
        <span class="s">--username "${CI_REGISTRY_USER}" \</span>
        <span class="s">--password "${CI_REGISTRY_PASSWORD}" \</span>
        <span class="s">"${CI_REGISTRY}"</span>

      <span class="s">buildah build \</span>
        <span class="s">-t ${IMAGE_NAME} \</span>
        <span class="s">--label=CI_COMMIT_SHA=${CI_COMMIT_SHA} \</span>
        <span class="s">-f ${PROJECT_DIR}/Dockerfile \</span>
        <span class="s">${PROJECT_DIR}/src</span>

      <span class="s">buildah push ${IMAGE_NAME}</span>

      <span class="s">if [ "${CI_COMMIT_BRANCH}" = "${CI_DEFAULT_BRANCH}" ]</span>
      <span class="s">then</span>
        <span class="s">buildah push "${IMAGE_NAME}" "${CI_REGISTRY_IMAGE}:latest"</span>
      <span class="s">fi</span>
</code></pre></div></div> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># subproject2/.gitlab-ci.yml</span>
<span class="na">subproject2</span><span class="pi">:</span>
  <span class="na">stage</span><span class="pi">:</span> <span class="s">build</span>
  <span class="na">image</span><span class="pi">:</span> <span class="s">quay.io/buildah/stable:latest</span>
  <span class="na">variables</span><span class="pi">:</span>
    <span class="na">PROJECT_DIR</span><span class="pi">:</span> <span class="s">subproject2</span>
    <span class="na">STORAGE_DRIVER</span><span class="pi">:</span> <span class="s">vfs</span>
    <span class="na">BUILDAH_FORMAT</span><span class="pi">:</span> <span class="s">oci</span>
    <span class="na">IMAGE_NAME</span><span class="pi">:</span> <span class="s">${CI_REGISTRY_IMAGE}/${CI_JOB_NAME_SLUG}:${CI_COMMIT_REF_SLUG}</span>
  <span class="na">script</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="pi">|</span>
      <span class="s">buildah login \</span>
        <span class="s">--username "${CI_REGISTRY_USER}" \</span>
        <span class="s">--password "${CI_REGISTRY_PASSWORD}" \</span>
        <span class="s">"${CI_REGISTRY}"</span>

      <span class="s">buildah build \</span>
        <span class="s">-t ${IMAGE_NAME} \</span>
        <span class="s">--label=CI_COMMIT_SHA=${CI_COMMIT_SHA} \</span>
        <span class="s">-f ${PROJECT_DIR}/Dockerfile \</span>
        <span class="s">${PROJECT_DIR}/src</span>

      <span class="s">buildah push ${IMAGE_NAME}</span>

      <span class="s">if [ "${CI_COMMIT_BRANCH}" = "${CI_DEFAULT_BRANCH}" ]</span>
      <span class="s">then</span>
        <span class="s">buildah push "${IMAGE_NAME}" "${CI_REGISTRY_IMAGE}:latest"</span>
      <span class="s">fi</span>
</code></pre></div></div>]]></content><author><name>Linda Jansson</name></author><category term="uncategorized"/><summary type="html"><![CDATA[GitLab pipelines in monorepo]]></summary></entry><entry><title type="html">Run podman containers as systemd services</title><link href="https://linda-jansson.com/run-podman-containers-as-systemd-services/" rel="alternate" type="text/html" title="Run podman containers as systemd services"/><published>2024-10-19T00:00:00+00:00</published><updated>2024-10-19T00:00:00+00:00</updated><id>https://linda-jansson.com/run-podman-containers-as-systemd-services</id><content type="html" xml:base="https://linda-jansson.com/run-podman-containers-as-systemd-services/"><![CDATA[<h1 id="run-podman-containers-as-systemd-services">Run podman containers as systemd services</h1> <p>The systemd unit files below are for starting a pod with containers as a non-root user.</p> <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># these have to be set for running systemctl with user scope</span>
<span class="nb">export </span><span class="nv">XDG_RUNTIME_DIR</span><span class="o">=</span>/run/user/<span class="si">$(</span><span class="nb">id</span> <span class="nt">-u</span><span class="si">)</span>
<span class="nb">export </span><span class="nv">XDG_CONFIG_HOME</span><span class="o">=</span><span class="k">${</span><span class="nv">HOME</span><span class="k">}</span>/.config

<span class="c"># directory to output podman logs</span>
<span class="nb">mkdir</span> ~/logs

<span class="c"># install user systemd service files</span>
<span class="nb">mv</span> <span class="k">*</span>.service <span class="s2">"</span><span class="k">${</span><span class="nv">XDG_CONFIG_HOME</span><span class="k">}</span><span class="s2">/systemd/user/"</span>
systemctl <span class="nt">--user</span> daemon-reload

<span class="c"># start the pod along with pod containers</span>
systemctl <span class="nt">--user</span> start pod.service
</code></pre></div></div> <h2 id="pod-service">Pod service</h2> <p>The systemd unit file for the pod creates and removes the pod.</p> <div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[Unit]</span><span class="w">
</span><span class="py">Description</span><span class="p">=</span><span class="s">Pod</span>
<span class="py">Wants</span><span class="p">=</span><span class="s">network.target</span>
<span class="py">After</span><span class="p">=</span><span class="s">network-online.target</span>
<span class="py">RequiresMountsFor</span><span class="p">=</span>
<span class="py">Requires</span><span class="p">=</span><span class="s">postgresql.service fluent-bit.service</span>
<span class="py">Before</span><span class="p">=</span><span class="s">postgresql.service fluent-bit.service</span>
<span class="w">
</span><span class="nn">[Service]</span><span class="w">
</span><span class="py">Type</span><span class="p">=</span><span class="s">forking</span>
<span class="py">RemainAfterExit</span><span class="p">=</span><span class="s">yes</span>
<span class="py">TimeoutStartSec</span><span class="p">=</span><span class="s">5m</span>
<span class="py">PIDFile</span><span class="p">=</span><span class="s">%t/%n.pid</span>
<span class="py">Restart</span><span class="p">=</span><span class="s">yes</span>
<span class="py">RestartSec</span><span class="p">=</span><span class="s">30</span>
<span class="w">
</span><span class="py">ExecStartPre</span><span class="p">=</span><span class="s">/bin/rm --force %t/%n.pid %t/%n-pod-id</span>
<span class="py">ExecStartPre</span><span class="p">=</span><span class="s">/usr/bin/podman pod create </span><span class="se">\
</span><span class="w">  </span><span class="s">--infra-conmon-pidfile %t/%n.pid </span><span class="se">\
</span><span class="w">  </span><span class="s">--pod-id-file %t/%n-pod-id </span><span class="se">\
</span><span class="w">  </span><span class="s">--name pod </span><span class="se">\
</span><span class="w">  </span><span class="s">--publish 2020:2020 </span><span class="se">\
</span><span class="w">  </span><span class="s">--publish 5432:5432 </span><span class="se">\
</span><span class="w">  </span><span class="s">--replace</span>
<span class="w">
</span><span class="py">ExecStart</span><span class="p">=</span><span class="s">/usr/bin/podman pod start --pod-id-file %t/%n-pod-id</span>
<span class="w">
</span><span class="py">ExecStop</span><span class="p">=</span><span class="s">/usr/bin/podman pod stop --ignore --pod-id-file %t/%n-pod-id --time 10</span>
<span class="w">
</span><span class="py">ExecStopPost</span><span class="p">=</span><span class="s">/usr/bin/podman pod rm --ignore --force --pod-id-file %t/%n-pod-id</span>
<span class="w">
</span><span class="nn">[Install]</span><span class="w">
</span><span class="py">WantedBy</span><span class="p">=</span><span class="s">default.target</span>
</code></pre></div></div> <h2 id="fluent-bit-service">Fluent-bit service</h2> <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir</span> <span class="s2">"</span><span class="k">${</span><span class="nv">XDG_CONFIG_HOME</span><span class="k">}</span><span class="s2">/fluent-bit"</span>
<span class="nb">touch</span> <span class="s2">"</span><span class="k">${</span><span class="nv">XDG_CONFIG_HOME</span><span class="k">}</span><span class="s2">/fluent-bit/fluent-bit.conf"</span>
<span class="nb">touch</span> <span class="s2">"</span><span class="k">${</span><span class="nv">XDG_CONFIG_HOME</span><span class="k">}</span><span class="s2">/fluent-bit/parsers.custom.conf"</span>

podman volume create fluent-bit-data
</code></pre></div></div> <div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[Unit]</span><span class="w">
</span><span class="py">Description</span><span class="p">=</span><span class="s">Podman fluent-bit</span>
<span class="py">Wants</span><span class="p">=</span><span class="s">network.target</span>
<span class="py">After</span><span class="p">=</span><span class="s">network-online.target</span>
<span class="py">RequiresMountsFor</span><span class="p">=</span><span class="s">%t/containers</span>
<span class="py">BindsTo</span><span class="p">=</span><span class="s">pod.service</span>
<span class="py">After</span><span class="p">=</span><span class="s">pod.service</span>
<span class="w">
</span><span class="nn">[Service]</span><span class="w">
</span><span class="py">Type</span><span class="p">=</span><span class="s">notify</span>
<span class="py">NotifyAccess</span><span class="p">=</span><span class="s">all</span>
<span class="py">TimeoutStartSec</span><span class="p">=</span><span class="s">5m</span>
<span class="py">TimeoutStopSec</span><span class="p">=</span><span class="s">70</span>
<span class="py">Environment</span><span class="p">=</span><span class="s">PODMAN_SYSTEMD_UNIT=%n</span>
<span class="py">Restart</span><span class="p">=</span><span class="s">yes</span>
<span class="py">RestartSec</span><span class="p">=</span><span class="s">30</span>
<span class="py">PIDFile</span><span class="p">=</span><span class="s">%t/%n.pid</span>
<span class="w">
</span><span class="py">ExecStartPre</span><span class="p">=</span><span class="s">/usr/bin/rm --force %t/%n.pid %t/%n-cid</span>
<span class="w">
</span><span class="py">ExecStart</span><span class="p">=</span><span class="s">/usr/bin/podman run </span><span class="se">\
</span><span class="w">  </span><span class="s">--cidfile %t/%n.cid </span><span class="se">\
</span><span class="w">  </span><span class="s">--cgroups=no-conmon </span><span class="se">\
</span><span class="w">  </span><span class="s">--rm </span><span class="se">\
</span><span class="w">  </span><span class="s">--sdnotify=conmon </span><span class="se">\
</span><span class="w">  </span><span class="s">--replace </span><span class="se">\
</span><span class="w">  </span><span class="s">--read-only </span><span class="se">\
</span><span class="w">  </span><span class="s">--pod pod </span><span class="se">\
</span><span class="w">  </span><span class="s">--volume fluent-bit-data:/var/data:U,Z,rw </span><span class="se">\
</span><span class="w">  </span><span class="s">--volume %h/logs:/var/log/containers:z,ro </span><span class="se">\
</span><span class="w">  </span><span class="s">--volume %E/fluent-bit/fluent-bit.conf:/fluent-bit/etc/fluent-bit.conf:z,ro </span><span class="se">\
</span><span class="w">  </span><span class="s">--volume %E/fluent-bit/parsers.custom.conf:/fluent-bit/etc/parser.custom.conf:z,ro </span><span class="se">\
</span><span class="w">  </span><span class="s">--name fluent-bit </span><span class="se">\
</span><span class="w">  </span><span class="s">docker.io/fluent/fluent-bit:3.1.9</span>
<span class="w">
</span><span class="py">ExecStop</span><span class="p">=</span><span class="s">/usr/bin/podman stop --ignore --cidfile %t/%n.cid --time 10</span>
<span class="w">
</span><span class="py">ExecStopPost</span><span class="p">=</span><span class="s">/usr/bin/podman rm --ignore --force --cidfile %t/%n.cid --time 10</span>
<span class="w">
</span><span class="nn">[Install]</span><span class="w">
</span><span class="py">WantedBy</span><span class="p">=</span><span class="s">default.target</span>
</code></pre></div></div> <h2 id="postgresql-service">PostgreSQL service</h2> <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir</span> <span class="s2">"</span><span class="k">${</span><span class="nv">XDG_CONFIG_HOME</span><span class="k">}</span><span class="s2">/postgresql"</span>
<span class="nb">touch</span> <span class="s2">"</span><span class="k">${</span><span class="nv">XDG_CONFIG_HOME</span><span class="k">}</span><span class="s2">/postgresql/app-password"</span>
<span class="nb">touch</span> <span class="s2">"</span><span class="k">${</span><span class="nv">XDG_CONFIG_HOME</span><span class="k">}</span><span class="s2">/postgresql/postgresql-password"</span>

podman volume create postgresql-data
</code></pre></div></div> <div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[Unit]</span><span class="w">
</span><span class="py">Description</span><span class="p">=</span><span class="s">Podman postgresql</span>
<span class="py">Wants</span><span class="p">=</span><span class="s">network.target</span>
<span class="py">After</span><span class="p">=</span><span class="s">network-online.target</span>
<span class="py">RequiresMountsFor</span><span class="p">=</span><span class="s">%t/containers</span>
<span class="py">BindsTo</span><span class="p">=</span><span class="s">pod.service</span>
<span class="py">After</span><span class="p">=</span><span class="s">pod.service</span>
<span class="w">
</span><span class="nn">[Service]</span><span class="w">
</span><span class="py">Type</span><span class="p">=</span><span class="s">notify</span>
<span class="py">NotifyAccess</span><span class="p">=</span><span class="s">all</span>
<span class="py">TimeoutStartSec</span><span class="p">=</span><span class="s">5m</span>
<span class="py">TimeoutStopSec</span><span class="p">=</span><span class="s">70</span>
<span class="py">Environment</span><span class="p">=</span><span class="s">PODMAN_SYSTEMD_UNIT=%n</span>
<span class="py">Restart</span><span class="p">=</span><span class="s">yes</span>
<span class="py">RestartSec</span><span class="p">=</span><span class="s">30</span>
<span class="py">PIDFile</span><span class="p">=</span><span class="s">%t/%n.pid</span>
<span class="w">
</span><span class="py">ExecStartPre</span><span class="p">=</span><span class="s">/usr/bin/rm --force %t/%n.pid %t/%n-cid</span>
<span class="w">
</span><span class="py">ExecStart</span><span class="p">=</span><span class="s">/usr/bin/podman run </span><span class="se">\
</span><span class="w">  </span><span class="s">--cidfile %t/%n.cid </span><span class="se">\
</span><span class="w">  </span><span class="s">--cgroups=no-conmon </span><span class="se">\
</span><span class="w">  </span><span class="s">--rm </span><span class="se">\
</span><span class="w">  </span><span class="s">--sdnotify=conmon </span><span class="se">\
</span><span class="w">  </span><span class="s">--replace </span><span class="se">\
</span><span class="w">  </span><span class="s">--read-only </span><span class="se">\
</span><span class="w">  </span><span class="s">--pod pod </span><span class="se">\
</span><span class="w">  </span><span class="s">--env POSTGRES_APP_DB=foi </span><span class="se">\
</span><span class="w">  </span><span class="s">--env POSTGRES_APP_USER=foi </span><span class="se">\
</span><span class="w">  </span><span class="s">--volume postgresql-data:/var/lib/postgresql/data:U,Z,rw </span><span class="se">\
</span><span class="w">  </span><span class="s">--volume "%E/postgresql/app-password:/run/secrets/app-password:z,ro" </span><span class="se">\
</span><span class="w">  </span><span class="s">--volume "%E/postgresql/postgres-password:/run/secrets/postgres-password:z,ro" </span><span class="se">\
</span><span class="w">  </span><span class="s">--log-driver=k8s-file </span><span class="se">\
</span><span class="w">  </span><span class="s">--log-opt path=%h/logs/postgresql.log </span><span class="se">\
</span><span class="w">  </span><span class="s">--log-opt max-size=1mb </span><span class="se">\
</span><span class="w">  </span><span class="s">--name postgresql </span><span class="se">\
</span><span class="w">  </span><span class="s">postgresql:latest</span>
<span class="w">
</span><span class="py">ExecStop</span><span class="p">=</span><span class="s">/usr/bin/podman stop --ignore --cidfile %t/%n.cid --time 10</span>
<span class="w">
</span><span class="py">ExecStopPost</span><span class="p">=</span><span class="s">/usr/bin/podman rm --ignore --force --cidfile %t/%n.cid --time 10</span>
<span class="w">
</span><span class="nn">[Install]</span><span class="w">
</span><span class="py">WantedBy</span><span class="p">=</span><span class="s">default.target</span>
</code></pre></div></div>]]></content><author><name>Linda Jansson</name></author><category term="containers"/><summary type="html"><![CDATA[Run podman containers as systemd services]]></summary></entry><entry><title type="html">Vim cheatsheet</title><link href="https://linda-jansson.com/vim-cheatsheet/" rel="alternate" type="text/html" title="Vim cheatsheet"/><published>2024-10-19T00:00:00+00:00</published><updated>2024-10-19T00:00:00+00:00</updated><id>https://linda-jansson.com/vim-cheatsheet</id><content type="html" xml:base="https://linda-jansson.com/vim-cheatsheet/"><![CDATA[<h1 id="vim-cheatsheet">Vim cheatsheet</h1> <table> <thead> <tr> <th style="text-align: left">Action command</th> <th style="text-align: left">Description</th> </tr> </thead> <tbody> <tr> <td style="text-align: left"><code class="language-plaintext highlighter-rouge">:help [subject]</code></td> <td style="text-align: left">Show help text on subject. E.g. <code class="language-plaintext highlighter-rouge">:help options</code>, <code class="language-plaintext highlighter-rouge">:help ZQ</code>, <code class="language-plaintext highlighter-rouge">:help key-notation</code>.</td> </tr> <tr> <td style="text-align: left"><code class="language-plaintext highlighter-rouge">:show SETTING</code></td> <td style="text-align: left">Show current value of setting.</td> </tr> <tr> <td style="text-align: left"><code class="language-plaintext highlighter-rouge">:source ~/.vimrc</code></td> <td style="text-align: left">Source vimrc file.</td> </tr> <tr> <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ZZ</code></td> <td style="text-align: left">Save and quit (<code class="language-plaintext highlighter-rouge">:wq</code>).</td> </tr> <tr> <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ZQ</code></td> <td style="text-align: left">Quit without saving (<code class="language-plaintext highlighter-rouge">:q!</code>).</td> </tr> <tr> <td style="text-align: left"><code class="language-plaintext highlighter-rouge">gg</code> / <code class="language-plaintext highlighter-rouge">G</code> / <code class="language-plaintext highlighter-rouge">nG</code></td> <td style="text-align: left">Go to first line / last line / line number <code class="language-plaintext highlighter-rouge">n</code>.</td> </tr> <tr> <td style="text-align: left"><code class="language-plaintext highlighter-rouge">&lt;C-d&gt;</code> / <code class="language-plaintext highlighter-rouge">&lt;C-u&gt;</code></td> <td style="text-align: left">Go down/up half a page.</td> </tr> <tr> <td style="text-align: left"><code class="language-plaintext highlighter-rouge">&lt;C-e&gt;</code> / <code class="language-plaintext highlighter-rouge">&lt;C-y&gt;</code></td> <td style="text-align: left">Scroll down/up without moving cursor (sticks to top/bottom).</td> </tr> <tr> <td style="text-align: left"><code class="language-plaintext highlighter-rouge">zz</code> / <code class="language-plaintext highlighter-rouge">zt</code> / <code class="language-plaintext highlighter-rouge">zb</code></td> <td style="text-align: left">Scroll to center the cursor.</td> </tr> <tr> <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ctrl+i</code> / <code class="language-plaintext highlighter-rouge">ctrl+o</code></td> <td style="text-align: left">Go to next/previous jump position.</td> </tr> <tr> <td style="text-align: left"><code class="language-plaintext highlighter-rouge">vip</code>, <code class="language-plaintext highlighter-rouge">vap</code></td> <td style="text-align: left">Select paragraph about the cursor. <code class="language-plaintext highlighter-rouge">vipip</code> to select more.</td> </tr> <tr> <td style="text-align: left"><code class="language-plaintext highlighter-rouge">&lt;C-v&gt;{I</code></td> <td style="text-align: left">Insert before lines in block. Can be used to insert comment signs.</td> </tr> <tr> <td style="text-align: left"><code class="language-plaintext highlighter-rouge">V</code></td> <td style="text-align: left">Select lines.</td> </tr> <tr> <td style="text-align: left"><code class="language-plaintext highlighter-rouge">gv</code></td> <td style="text-align: left">Re-select last last selection.</td> </tr> <tr> <td style="text-align: left"><code class="language-plaintext highlighter-rouge">'.</code> <code class="language-plaintext highlighter-rouge">1v</code></td> <td style="text-align: left">Go to last edit and re-select the same number of characters/lines.</td> </tr> <tr> <td style="text-align: left"><code class="language-plaintext highlighter-rouge">.</code></td> <td style="text-align: left">Repeat last action.</td> </tr> <tr> <td style="text-align: left"><code class="language-plaintext highlighter-rouge">/term\c</code> <code class="language-plaintext highlighter-rouge">n</code> <code class="language-plaintext highlighter-rouge">N</code></td> <td style="text-align: left">Search for term case-insensitive, go to next, go to previous. <code class="language-plaintext highlighter-rouge">\C</code> for case-sensitive.</td> </tr> <tr> <td style="text-align: left"><code class="language-plaintext highlighter-rouge">/\%Vterm</code></td> <td style="text-align: left">Search for term in selection. Must be in normal mode, not visual.</td> </tr> <tr> <td style="text-align: left"><code class="language-plaintext highlighter-rouge">:g/regex</code></td> <td style="text-align: left">Search for regex and list results.</td> </tr> <tr> <td style="text-align: left"><code class="language-plaintext highlighter-rouge">:%s/PATTERN/REPLACEMENT/gc</code></td> <td style="text-align: left">Search and replace all occurrences.</td> </tr> <tr> <td style="text-align: left"><code class="language-plaintext highlighter-rouge">/term</code> <code class="language-plaintext highlighter-rouge">cgn</code> <code class="language-plaintext highlighter-rouge">...</code></td> <td style="text-align: left">Search and replace term (one by one). <code class="language-plaintext highlighter-rouge">n</code> to skip to next term.</td> </tr> <tr> <td style="text-align: left"><code class="language-plaintext highlighter-rouge">:noh[lsearch]</code></td> <td style="text-align: left">Stop highlighting search terms.</td> </tr> <tr> <td style="text-align: left"><code class="language-plaintext highlighter-rouge">/&lt;C-r&gt;"</code></td> <td style="text-align: left">Search for yanked text.</td> </tr> <tr> <td style="text-align: left"><code class="language-plaintext highlighter-rouge">gJ</code></td> <td style="text-align: left">Join line from with below.</td> </tr> <tr> <td style="text-align: left"><code class="language-plaintext highlighter-rouge">:%!clang-format</code></td> <td style="text-align: left">Run command <code class="language-plaintext highlighter-rouge">clang-format</code> on text.</td> </tr> <tr> <td style="text-align: left"><code class="language-plaintext highlighter-rouge">:'&lt;,'&gt;!awk -f script.awk</code></td> <td style="text-align: left">Run command <code class="language-plaintext highlighter-rouge">awk</code> on visual selection.</td> </tr> <tr> <td style="text-align: left"><code class="language-plaintext highlighter-rouge">:read !date</code></td> <td style="text-align: left">Insert output of command <code class="language-plaintext highlighter-rouge">date</code>.</td> </tr> <tr> <td style="text-align: left"><code class="language-plaintext highlighter-rouge">:read FILE</code></td> <td style="text-align: left">Insert contents of file <code class="language-plaintext highlighter-rouge">FILE</code>.</td> </tr> <tr> <td style="text-align: left"><code class="language-plaintext highlighter-rouge">:new | 0read ! git diff #</code></td> <td style="text-align: left">Open a file’s git diff in new window pane.</td> </tr> <tr> <td style="text-align: left"><code class="language-plaintext highlighter-rouge">:Lexplore</code></td> <td style="text-align: left">Open netrw in left pane.</td> </tr> <tr> <td style="text-align: left"><code class="language-plaintext highlighter-rouge">&lt;C-w&gt;w</code></td> <td style="text-align: left">Cycle pane focus.</td> </tr> <tr> <td style="text-align: left"><code class="language-plaintext highlighter-rouge">&lt;C-w&gt;l</code></td> <td style="text-align: left">Focus on left pane.</td> </tr> <tr> <td style="text-align: left"><code class="language-plaintext highlighter-rouge">&lt;C-w&gt;|</code> / <code class="language-plaintext highlighter-rouge">&lt;C-w&gt;_</code></td> <td style="text-align: left">Maximize size of pane width/height.</td> </tr> <tr> <td style="text-align: left"><code class="language-plaintext highlighter-rouge">&lt;C-w&gt;=</code></td> <td style="text-align: left">Equalize sizes of all panes.</td> </tr> <tr> <td style="text-align: left"><code class="language-plaintext highlighter-rouge">&lt;C-w&gt;10&gt;</code> / <code class="language-plaintext highlighter-rouge">&lt;C-w&gt;-10&gt;</code></td> <td style="text-align: left">Increase/decrease pane width by 10 columns.</td> </tr> </tbody> </table> <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># recover file from swapfile (see :help recovery / :help -r)</span>
vim <span class="nt">-r</span> .swp
</code></pre></div></div> <h2 id="vimrc"><code class="language-plaintext highlighter-rouge">~/.vimrc</code></h2> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>set exrc      " source .vimrc in project folders
set mouse=    " disable mouse
set list      " show listchars for tabs, trailing, nbsp
set listchars=tab:&gt;&gt;,trail:-,nbsp:+
set number relativenumber " hybrid line numbers (i.e. relative with current line as absolute)
set ignorecase smartcase  " search case-insensitive except when any character if uppercase

set tabstop=4 softtabstop=4 shiftwidth=4 expandtab smarttab
set autoindent         " indent new line as previous line
set formatoptions-=t   " don't auto-wrap text
set formatoptions-=c   " don't auto-wrap comments
set formatoptions-=r   " don't automatically insert comment leader on Enter
set fileencodings=utf-8,latin1 " default file encodings

let g:netrw_banner = 0         " disable banner
let g:netrw_liststyle = 3      " display as tree
let g:netrw_browse_split = 0   " reuse window when opening files
let g:netrw_winsize = 20       " explorer window size percentage

colorscheme slate

" Show file information in status line
set laststatus=2
set statusline=%f%=%y\ %{&amp;fileformat}\ %{&amp;fileencoding==\"\"?&amp;encoding:&amp;fileencoding}\ \ L%l:C%c\ 

set scrolloff=999 " screen lines kept above/below cursor (=999 to keep cursor in middle)

" use Space key as mapleader
nnoremap &lt;space&gt; &lt;nop&gt;
let mapleader=" "
set timeoutlen=2000 " 

nnoremap &lt;leader&gt;m :marks&lt;CR&gt;

" remap shift+tab to de-indent
inoremap &lt;S-Tab&gt; &lt;C-O&gt;&lt;&lt;

" show matching parentheses with highlighted foreground, not background
hi MatchParen ctermfg=NONE ctermbg=NONE cterm=bold

" auto-save files under home
augroup AUTOSAVE
    autocmd!
    autocmd TextChanged,InsertLeave,FocusLost ~/* if &amp;readonly == 0 &amp;&amp; filereadable(bufname('%')) | silent update | endif
augroup END

" remove autosave if environment variable
if $VIM_AUTOSAVE=="no"
    au! AUTOSAVE
endif
</code></pre></div></div> <h3 id="auto-complete-parentheses">Auto-complete parentheses</h3> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>" auto-complete parentheses
inoremap {&lt;space&gt; {}&lt;left&gt;
inoremap (&lt;space&gt; ()&lt;left&gt;
inoremap [&lt;space&gt; []&lt;left&gt;
inoremap "&lt;space&gt; ""&lt;left&gt;
inoremap '&lt;space&gt; ''&lt;left&gt;
" except when completing manually
inoremap {} {}
inoremap () ()
inoremap [] []
inoremap "" ""
" auto-add closing bracket after enter
inoremap {&lt;CR&gt; {&lt;CR&gt;}&lt;up&gt;&lt;CR&gt;
</code></pre></div></div> <h2 id="vimcolorscustomvim"><code class="language-plaintext highlighter-rouge">~/.vim/colors/custom.vim</code></h2> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>set background=dark

" clear highlighting
hi clear
if exists("syntax_on")
    syntax reset
endif

"set t_Co=256
let g:colors_name = "simple"

"hi CTagsClass           -- no settings --
"hi CTagsGlobalConstant  -- no settings --
"hi CTagsGlobalVariable  -- no settings --
"hi CTagsImport          -- no settings --
"hi CTagsMember          -- no settings --
"hi DefinedName          -- no settings --
"hi EnumerationName      -- no settings --
"hi EnumerationValue     -- no settings --
"hi FoldColumn           -- no settings --
"hi Ignore               -- no settings --
"hi LocalVariable        -- no settings --
"hi ModeMsg              -- no settings --
"hi MoreMsg              -- no settings --
"hi Question             -- no settings --
"hi SpellBad             -- no settings --
"hi SpellCap             -- no settings --
"hi SpellLocal           -- no settings --
"hi SpellRare            -- no settings --
"hi StatusLine           -- no settings --
"hi StatusLineNC         -- no settings --
"hi Union                -- no settings --
"hi VisualNOS            -- no settings --
"hi WarningMsg           -- no settings --
"hi clear                -- no settings --

 
" http://vimdoc.sourceforge.net/htmldoc/syntax.html#:highlight
hi Normal                ctermfg=15    ctermbg=0     cterm=NONE       guifg=#000000 guibg=#ffffff gui=NONE

hi Boolean               ctermfg=8     ctermbg=NONE  cterm=NONE       guifg=#ff0000 guibg=NONE gui=NONE
hi Character             ctermfg=2     ctermbg=NONE  cterm=NONE       guifg=#2323ce guibg=NONE gui=NONE
hi Comment               ctermfg=7     ctermbg=NONE  cterm=NONE       guifg=#666666 guibg=NONE gui=NONE
hi Conditional           ctermfg=NONE  ctermbg=NONE  cterm=bold       guifg=NONE guibg=NONE gui=bold
hi Constant              ctermfg=2     ctermbg=NONE  cterm=NONE       guifg=#2323ce guibg=NONE gui=NONE
hi CursorColumn          ctermfg=NONE  ctermbg=NONE  cterm=underline  guifg=NONE guibg=NONE gui=NONE
hi CursorLine            ctermfg=NONE  ctermbg=NONE  cterm=NONE       guifg=NONE guibg=NONE gui=NONE
hi CursorLineNr          ctermfg=2     ctermbg=NONE  cterm=NONE       guifg=#2323ce guibg=NONE gui=NONE
hi ColorColumn           ctermfg=2     ctermbg=NONE  cterm=NONE       guifg=NONE guibg=#dddddd gui=NONE
hi Debug                 ctermfg=2     ctermbg=NONE  cterm=NONE       guifg=#2323ce guibg=NONE gui=NONE
hi Define                ctermfg=2     ctermbg=NONE  cterm=NONE       guifg=#2323ce guibg=NONE gui=NONE
hi Delimiter             ctermfg=8     ctermbg=NONE  cterm=NONE       guifg=#666666 guibg=NONE gui=NONE
hi DiffAdd               ctermfg=10    ctermbg=NONE  cterm=NONE       guifg=#00ff00 guibg=NONE gui=NONE
hi DiffChange            ctermfg=12    ctermbg=NONE  cterm=NONE       guifg=#0000ff guibg=NONE gui=NONE
hi DiffDelete            ctermfg=9     ctermbg=NONE  cterm=NONE       guifg=#ff0000 guibg=NONE gui=NONE
hi DiffText              ctermfg=NONE  ctermbg=NONE  cterm=NONE       guifg=NONE guibg=NONE gui=NONE
hi Directory             ctermfg=NONE  ctermbg=NONE  cterm=bold       guifg=NONE guibg=NONE gui=bold
hi Error                 ctermfg=1     ctermbg=NONE  cterm=NONE       guifg=#ff0000 guibg=NONE gui=NONE
hi ErrorMsg              ctermfg=1     ctermbg=NONE  cterm=NONE       guifg=#ff0000 guibg=NONE gui=NONE
hi Exception             ctermfg=NONE  ctermbg=NONE  cterm=bold       guifg=NONE guibg=NONE gui=italic
hi Float                 ctermfg=2     ctermbg=NONE  cterm=NONE       guifg=#2323ce guibg=NONE gui=NONE
hi Folded                ctermfg=NONE  ctermbg=8     cterm=bold       guifg=NONE guibg=#cccccc gui=NONE
hi Function              ctermfg=NONE  ctermbg=NONE  cterm=bold       guifg=NONE guibg=NONE gui=bold
hi HighlightedyankRegion ctermfg=NONE  ctermbg=NONE  cterm=bold       guifg=NONE guibg=NONE gui=italic
hi htmltagname           ctermfg=8     ctermbg=NONE  cterm=NONE       guifg=#666666 guibg=NONE gui=NONE
hi Identifier            ctermfg=NONE  ctermbg=NONE  cterm=bold       guifg=NONE guibg=NONE gui=bold
hi IncSearch             ctermfg=NONE  ctermbg=NONE  cterm=inverse,bold guifg=NONE guibg=NONE gui=inverse,italic
hi Include               ctermfg=NONE  ctermbg=NONE  cterm=bold       guifg=NONE guibg=NONE gui=bold
hi Keyword               ctermfg=NONE  ctermbg=NONE  cterm=bold       guifg=NONE guibg=NONE gui=bold
hi Label                 ctermfg=NONE  ctermbg=NONE  cterm=bold       guifg=NONE guibg=NONE gui=bold
hi LineNr                ctermfg=8     ctermbg=NONE  cterm=NONE       guifg=#666666 guibg=NONE gui=NONE
hi Macro                 ctermfg=2     ctermbg=NONE  cterm=NONE       guifg=#2323ce guibg=NONE gui=NONE
hi MatchParen            ctermfg=2     ctermbg=NONE  cterm=NONE       guifg=#2323ce guibg=NONE gui=bold
hi ModeMsg               ctermfg=8     ctermbg=NONE  cterm=NONE       guifg=#666666 guibg=NONE gui=NONE
hi NonText               ctermfg=8     ctermbg=NONE  cterm=NONE       guifg=#666666 guibg=NONE gui=NONE
hi Number                ctermfg=2     ctermbg=NONE  cterm=NONE       guifg=#2323ce guibg=NONE gui=NONE
hi Operator              ctermfg=8     ctermbg=NONE  cterm=NONE       guifg=#666666 guibg=NONE gui=NONE
hi PMenu                 ctermfg=NONE  ctermbg=NONE  cterm=NONE       guifg=NONE guibg=NONE gui=NONE
hi PMenuSbar             ctermfg=NONE  ctermbg=NONE  cterm=NONE       guifg=NONE guibg=NONE gui=NONE
hi PMenuSel              ctermfg=2     ctermbg=NONE  cterm=NONE       guifg=#2323ce guibg=NONE gui=NONE
hi PMenuThumb            ctermfg=NONE  ctermbg=NONE  cterm=NONE       guifg=NONE guibg=NONE gui=NONE
hi PreCondit             ctermfg=6     ctermbg=NONE  cterm=NONE       guifg=#16b723 guibg=NONE gui=NONE
hi PreProc               ctermfg=5     ctermbg=NONE  cterm=NONE       guifg=#333333 guibg=NONE gui=NONE
hi pythonescape          ctermfg=NONE  ctermbg=NONE  cterm=NONE       guifg=NONE guibg=NONE gui=NONE
hi Repeat                ctermfg=NONE  ctermbg=NONE  cterm=bold       guifg=NONE guibg=NONE gui=bold
hi Search                ctermfg=NONE  ctermbg=NONE  cterm=inverse    guifg=NONE guibg=NONE gui=inverse
hi SignColumn            ctermfg=NONE  ctermbg=NONE  cterm=inverse    guifg=NONE guibg=NONE gui=inverse
hi Special               ctermfg=2     ctermbg=NONE  cterm=bold       guifg=#2323ce guibg=NONE gui=bold
hi SpecialChar           ctermfg=2     ctermbg=NONE  cterm=bold       guifg=#2323ce guibg=NONE gui=bold
hi SpecialComment        ctermfg=2     ctermbg=NONE  cterm=bold       guifg=#2323ce guibg=NONE gui=bold
hi SpecialKey            ctermfg=8     ctermbg=NONE  cterm=bold       guifg=#666666 guibg=NONE gui=bold
hi Statement             ctermfg=NONE  ctermbg=NONE  cterm=NONE       guifg=NONE guibg=NONE gui=NONE
hi StatusLine            ctermfg=NONE  ctermbg=NONE  cterm=NONE       guifg=#666666 guibg=NONE gui=NONE
hi StatusLineNC          ctermfg=8     ctermbg=NONE  cterm=NONE       guifg=#333333 guibg=NONE gui=NONE
hi StorageClass          ctermfg=NONE  ctermbg=NONE  cterm=bold       guifg=NONE guibg=NONE gui=bold
hi String                ctermfg=2     ctermbg=NONE  cterm=NONE       guifg=#2323ce guibg=NONE gui=NONE
hi Structure             ctermfg=NONE  ctermbg=NONE  cterm=bold       guifg=NONE guibg=NONE gui=bold
hi TabLine               ctermfg=NONE  ctermbg=NONE  cterm=NONE       guifg=NONE guibg=NONE gui=NONE
hi TabLineFill           ctermfg=NONE  ctermbg=8     cterm=NONE       guifg=NONE guibg=#666666 gui=NONE
hi TabLineSel            ctermfg=NONE  ctermbg=NONE  cterm=bold       guifg=NONE guibg=NONE gui=bold
hi Tag                   ctermfg=NONE  ctermbg=NONE  cterm=NONE       guifg=NONE guibg=NONE gui=underline
hi Title                 ctermfg=NONE  ctermbg=NONE  cterm=bold       guifg=NONE guibg=NONE gui=NONE
hi Todo                  ctermfg=NONE  ctermbg=NONE  cterm=bold       guifg=NONE guibg=NONE gui=bold
hi Type                  ctermfg=NONE  ctermbg=NONE  cterm=bold       guifg=NONE guibg=NONE gui=bold
hi Typedef               ctermfg=NONE  ctermbg=NONE  cterm=bold       guifg=NONE guibg=NONE gui=bold
hi Underlined            ctermfg=NONE  ctermbg=NONE  cterm=underline  guifg=NONE guibg=NONE gui=underline
hi VertSplit             ctermfg=NONE  ctermbg=NONE  cterm=NONE       guifg=#333333 guibg=NONE gui=NONE
hi Visual                ctermfg=NONE  ctermbg=NONE  cterm=inverse    guifg=NONE guibg=NONE gui=inverse
hi WildMenu              ctermfg=2     ctermbg=NONE  cterm=NONE       guifg=#2323ce guibg=NONE gui=NONE
</code></pre></div></div> <p>Check current value with e.g. <code class="language-plaintext highlighter-rouge">:highlight Comment</code>.</p> <h2 id="re-orders-lines">Re-orders lines</h2> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dd          Delete (and yank) the lines in the order they should be in
:display    Display registers
"1P         Paste from register 1 above current line
.           Repeat the paste action while updating register 1 to the next register
</code></pre></div></div>]]></content><author><name>Linda Jansson</name></author><category term="cheatsheet"/><summary type="html"><![CDATA[Vim cheatsheet]]></summary></entry><entry><title type="html">Unicode encoding</title><link href="https://linda-jansson.com/unicode-encoding/" rel="alternate" type="text/html" title="Unicode encoding"/><published>2024-09-08T00:00:00+00:00</published><updated>2024-09-08T00:00:00+00:00</updated><id>https://linda-jansson.com/unicode-encoding</id><content type="html" xml:base="https://linda-jansson.com/unicode-encoding/"><![CDATA[<h1 id="unicode-encoding">Unicode encoding</h1> <p>The Unicode standard encodes characters as 21-bit codepoint values. The codepoints are stored in one of the formats: UTF-32, UTF-16, or UTF-8.</p> <ul> <li>UTF-32 stores the 21-bit unicode data in a 32-bit integer with 11 bits of zero-padding.</li> <li>UTF-16 stores unicode data in either a single 16-bit integer or within two 16-bit integers (called a surrogate pair). The high 16-bit word and the low 16-bit word each has 10 bits for the unicode data – which is only 20 bits and not the full 21 bits. The other bits are used to recognize the encoding.</li> <li>UTF-8 stores 21-bit values within one, two, three, or four 8-bit integers. For multibyte sequences the initial bits of the first byte identify the sequence length. Subsequent bytes start with <code class="language-plaintext highlighter-rouge">10</code> followed by 6 bits for unicode data.</li> </ul> <p>The diagram belows shows how Unicode codepoint values are encoded in bits for the different encoding formats. <code class="language-plaintext highlighter-rouge">x</code> is part of the codepoint bit number and the literal numbers are what identifies the Unicode encoding.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>          Codepoint range:       Bit encoding:
UTF-32:   0x0 - 0x1F FFFF        0000 0000 000x xxxx xxxx xxxx xxxx xxxx

UTF-16:   0x0000 - 0xD7FF        xxxx xxxx xxxx xxxx
          0xD800 - 0xDFFF        (not valid codepoints)
          0xE000 - 0xFFFF        xxxx xxxx xxxx xxxx
          0x010000 - 0x10FFFF    1101 10xx xxxx xxxx, 1101 11xx xxxx xxxx

UTF-8:    0x0 - 0x7F             0xxx xxxx
          0x80 - 0x7FF           110x xxxx, 10xx xxxx
          0x800 - 0xFFFF         1110 xxxx, 10xx xxxx, 10xx xxxx
          0x10000 - 0x1FFFFF     1111 0xxx, 10xx xxxx, 10xx xxxx, 10xx xxxx
</code></pre></div></div> <h2 id="mutf-8">MUTF-8</h2> <p>Codepoints should be encoded into the minimum length byte sequences for UTF-16 and UTF-8, but it’s possible to encode them with more bytes than necessary by simply setting the codepoint value bits to zeroes for the higher bytes.</p> <p>For example, 0 can be encoded with two byte sequences (11 data bits) instead of one as: <code class="language-plaintext highlighter-rouge">1100 0000 1000 0000</code> (<code class="language-plaintext highlighter-rouge">0xC080</code>). The Modified UTF-8 encoding (MUTF-8) uses this “overlong encoding” for the <code class="language-plaintext highlighter-rouge">NULL</code> value to avoid it being encoded (and possibly treated) as the end-of-string (i.e. <code class="language-plaintext highlighter-rouge">\0</code>).</p> <h2 id="byte-order-mark-bom">Byte-order-mark (BOM)</h2> <p>A byte-order-mark (BOM) in the beginning of the character stream marks the byte endianness of the sequence. This is relevant for UTF-16 and UTF-32 to recognize if the first byte of the encoded sequence is in the high byte or in the low byte of the integer – as the endianness of the system must match the sequence to load the bytes into 16/32-bit integers correctly.</p> <p>For UTF-8 there is no need for a BOM, since each byte is loaded into a separate integer. However, there is an optional (and discouraged) BOM for UTF-8 that can be used to identify the encoding as UTF-8.</p> <p>The Unicode codepoint for the BOM is <code class="language-plaintext highlighter-rouge">U+FEFF</code>, which is the “zero width non-breaking space” character.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>            First bytes    Check
UTF-32LE    FF FE 00 00    uint32_t bom == 0xFFFE0000
UTF-32BE    00 00 FE FF    uint32_t bom == 0x0000FEFF
UTF-16LE    FF FE .. ..    uint16_t bom == 0xFFFE
UTF-16BE    FE FF .. ..    uint16_t bom == 0xFEFF
UTF-8       EF BB BF ..    1110 [1111], 10[11 1011], 10[11 1111] = 0xFEFF
</code></pre></div></div> <p>Most CPU systems are little endian.</p>]]></content><author><name>Linda Jansson</name></author><category term="encoding"/><summary type="html"><![CDATA[Unicode encoding]]></summary></entry></feed>