<?xml version="1.0" encoding="utf-8" ?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>0ink.net</title>
<subtitle>Not the site you are looking for...</subtitle>
<link href="https://www.0ink.net"></link>
<link href="https://www.0ink.net/feeds/atom.xml" rel="self" type="application/atom+xml"></link>
<updated>2026-05-19T10:10:25+02:00</updated>
<logo>https://www.0ink.net/images/2021/0ink.png</logo>
<author><name>Alejandro Liu</name>
</author>
<id>urn:uuid:93d8401b-95a4-ecff-657d-d0de2d7da659</id>
<entry>
<title>Ansible Custom Action Plugins</title>
<link href="https://www.0ink.net/posts/2026/2026-05-15-ansible-action-plugins.html"></link>
<id>urn:uuid:a2967dc5-13f7-685b-8214-01d14d61897e</id>
<updated>2025-09-19T00:00:00+02:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Introduction
Motivation
Where to place Action Plugins

Role or Collection scope
...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Introduction&quot;&gt;Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Motivation&quot;&gt;Motivation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Where+to+place+Action+Plugins&quot;&gt;Where to place Action Plugins&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Role+or+Collection+scope&quot;&gt;Role or Collection scope&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#User+or+System+Scoped&quot;&gt;User or System Scoped&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Configuring+Plugin+Paths&quot;&gt;Configuring Plugin Paths&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Basic+layout&quot;&gt;Basic layout&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Error+handling&quot;&gt;Error handling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Diagnostics+and+Verbose+output&quot;&gt;Diagnostics and Verbose output&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Run-time+environment&quot;&gt;Run-time environment&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Execution+Context+Flags&quot;&gt;Execution Context Flags&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Path+Resolution&quot;&gt;Path Resolution&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Remote+File+Cleanup&quot;&gt;Remote File Cleanup&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Accessing+task+parameters&quot;&gt;Accessing task parameters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#More+Task+Context+via+task_vars&quot;&gt;More Task Context via task_vars&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Implementing+check+mode&quot;&gt;Implementing check mode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Implementing+diff+mode&quot;&gt;Implementing diff mode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Template+Rendering&quot;&gt;Template Rendering&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Calling+Modules+from+Action+Plugins&quot;&gt;Calling Modules from Action Plugins&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Example%3A+Delegating+to+a+Custom+Module&quot;&gt;Example: Delegating to a Custom Module&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Key+Concepts&quot;&gt;Key Concepts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Return+Value&quot;&gt;Return Value&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Best+Practices&quot;&gt;Best Practices&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Return+values&quot;&gt;Return values&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Common+Keys&quot;&gt;Common Keys&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Custom+Return+Values&quot;&gt;Custom Return Values&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;p&gt;This is a continuation to my &lt;a href=&quot;/posts/2026/2026-05-01-ansible-custom-modules.html&quot;&gt;Ansible custom modules&lt;/a&gt; article.&lt;/p&gt;
&lt;p&gt;Action plugins let you integrate local processing and local data with module functionality.&lt;/p&gt;
&lt;p&gt;At the time of this writing, documentation I could find was
&lt;a href=&quot;https://docs.ansible.com/ansible/latest/dev_guide/developing_plugins.html#action-plugins&quot;&gt;Developing plugins - Action plugins&lt;/a&gt; which was somewhat out-of-date.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/ansible_logo2.png&quot; alt=&quot;ansible logo&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;Introduction&quot; name=&quot;Introduction&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Ansible &lt;strong&gt;action plugins&lt;/strong&gt; are Python-based extensions that run on the &lt;strong&gt;control node&lt;/strong&gt;,
allowing you to intercept and modify the behavior of tasks before they&#039;re executed on
remote hosts. Unlike regular modules, which run remotely, action plugins give you local
control over task execution logic, argument manipulation, and result handling.&lt;/p&gt;
&lt;p&gt;They’re especially useful when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You need to &lt;strong&gt;preprocess arguments&lt;/strong&gt; or &lt;strong&gt;include local data&lt;/strong&gt; before calling a module.&lt;/li&gt;
&lt;li&gt;You want to &lt;strong&gt;combine multiple modules&lt;/strong&gt; into a single task.&lt;/li&gt;
&lt;li&gt;You’re working in environments with &lt;strong&gt;limited remote capabilities&lt;/strong&gt;, or need
&lt;strong&gt;local-only logic&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each plugin is a class that inherits from &lt;code&gt;ActionBase&lt;/code&gt;, and typically overrides the
&lt;code&gt;run()&lt;/code&gt; method. Inside this method, you can use &lt;code&gt;_execute_module()&lt;/code&gt; to call the actual
module, or bypass it entirely for local-only tasks.&lt;/p&gt;
&lt;h2 id=&quot;Motivation&quot; name=&quot;Motivation&quot;&gt;Motivation&lt;/h2&gt;
&lt;p&gt;While building an Ansible role for nftables, incorporating ideas from
&lt;a href=&quot;https://github.com/Frzk/ansible-role-nftables&quot;&gt;Frzk’s nftables role&lt;/a&gt;
and
&lt;a href=&quot;https://github.com/kormat/ansible-iptables-apply&quot;&gt;kormat’s iptables-apply&lt;/a&gt;
to handle recovery from new rulesets that would break or hang Ansible&#039;s
control connection it became challeging to use YAML to manage control flow,
error handling, and connection state.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/ansible-actions/nflogo.png&quot; alt=&quot;netfilter logo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;By writing a custom action plugin, I could:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Run logic locally on the control node&lt;/li&gt;
&lt;li&gt;Use Python to apply rules and handle failures gracefully&lt;/li&gt;
&lt;li&gt;Keep playbooks declarative, while isolating recovery logic in code&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This gives a cleaner, more robust way to experiment with firewall changes without
compromising automation reliability.&lt;/p&gt;
&lt;h2 id=&quot;Where+to+place+Action+Plugins&quot; name=&quot;Where+to+place+Action+Plugins&quot;&gt;Where to place Action Plugins&lt;/h2&gt;
&lt;p&gt;Custom action plugins can be placed in several locations depending on your project structure
and scope:&lt;/p&gt;
&lt;h3 id=&quot;Role+or+Collection+scope&quot; name=&quot;Role+or+Collection+scope&quot;&gt;Role or Collection scope&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Inside a role&lt;/strong&gt;:&lt;br /&gt;
Place the plugin in &lt;code&gt;roles/&amp;lt;role_name&amp;gt;/action_plugins/&lt;/code&gt;.&lt;br /&gt;
This makes it available only when the role is used.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Inside a collection&lt;/strong&gt;:&lt;br /&gt;
Use &lt;code&gt;plugins/action/&lt;/code&gt; within your collection directory.&lt;br /&gt;
This is ideal for distributing reusable plugins.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;User+or+System+Scoped&quot; name=&quot;User+or+System+Scoped&quot;&gt;User or System Scoped&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;User-level plugin directory&lt;/strong&gt;:&lt;br /&gt;
&lt;code&gt;~/.ansible/plugins/action/&lt;/code&gt;&lt;br /&gt;
This makes the plugin available across all playbooks for the current user.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;System-level plugin directory&lt;/strong&gt;:&lt;br /&gt;
&lt;code&gt;/usr/share/ansible/plugins/action/&lt;/code&gt;&lt;br /&gt;
Useful for global availability across users and projects.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/ansible-actions/plugins.png&quot; alt=&quot;plugins&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;Configuring+Plugin+Paths&quot; name=&quot;Configuring+Plugin+Paths&quot;&gt;Configuring Plugin Paths&lt;/h3&gt;
&lt;p&gt;To make Ansible recognize custom plugin locations, you can configure the plugin path:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Via &lt;code&gt;ansible.cfg&lt;/code&gt;&lt;/strong&gt; &lt;br /&gt;Add or modify the following in your &lt;code&gt;ansible.cfg&lt;/code&gt; file:
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;[defaults]
action_plugins = ./action_plugins:~/.ansible/plugins/action:/usr/share/ansible/plugins/action&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Paths are colon-separated.&lt;/li&gt;
&lt;li&gt;Relative paths (like &lt;code&gt;./action_plugins&lt;/code&gt;) are resolved from the playbook directory.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Via Environment Variable&lt;/strong&gt; &lt;br /&gt;Set the &lt;code&gt;ANSIBLE_ACTION_PLUGINS&lt;/code&gt; environment variable:
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;export ANSIBLE_ACTION_PLUGINS=./action_plugins:~/.ansible/plugins/action&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This overrides the config file setting and is useful for temporary or dynamic setups.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can inspect your current plugin paths using &lt;code&gt;ansible-config dump | grep ACTION_PLUGIN_PATH&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;Basic+layout&quot; name=&quot;Basic+layout&quot;&gt;Basic layout&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;#!/usr/bin/python
# Make coding more python3-ish, this is required for contributions to Ansible
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

# Base class
from ansible.plugins.action import ActionBase

class ActionModule(ActionBase):
  def run(self, tmp=None, task_vars=None):
    if task_vars is None: task_vars = dict()
    result = super().run(tmp, task_vars)
    del tmp  # tmp no longer has any effect

    # Do something here...

    return result
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the simplest action plugin.  It declares itself as a sub-class
of &lt;code&gt;ActionBase&lt;/code&gt;, and populates the &lt;code&gt;task_vars&lt;/code&gt; structure with values
and initializes &lt;code&gt;result&lt;/code&gt; with some defaults.&lt;/p&gt;
&lt;h2 id=&quot;Error+handling&quot; name=&quot;Error+handling&quot;&gt;Error handling&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/ansible-actions/errors.png&quot; alt=&quot;errors&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Ansible leverages Python&#039;s exception system to handle errors cleanly. Within your custom
action plugin, you can raise exceptions to signal failure conditions. Start by importing
the relevant classes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from ansible.errors import AnsibleError, AnsibleFileNotFound, AnsibleAction, AnsibleActionFail&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also use Python’s built-in &lt;a href=&quot;https://docs.python.org/3/library/exceptions.html&quot;&gt;exceptions&lt;/a&gt; if preferred.&lt;/p&gt;
&lt;p&gt;To raise an error:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;raise AnsibleFileNotFound(&#039;thisfile.txt&#039;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By default, Ansible displays a simplified error message. To see the full
&lt;strong&gt;stack trace&lt;/strong&gt;, run your playbook with increased verbosity:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ansible-playbook playbook.yml -vvv&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is especially useful when debugging plugin logic or tracing failures deep in
the call stack.&lt;/p&gt;
&lt;h2 id=&quot;Diagnostics+and+Verbose+output&quot; name=&quot;Diagnostics+and+Verbose+output&quot;&gt;Diagnostics and Verbose output&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/ansible-actions/diagnostics.png&quot; alt=&quot;diagnostics&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Custom action plugins often need to communicate status or debug information. Ansible
provides the &lt;code&gt;Display&lt;/code&gt; class for structured output:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from ansible.utils.display import Display
display = Display()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can then emit messages based on verbosity level:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;if display.verbosity &amp;gt; 1:
    display.display(&#039;Verbose message&#039;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or use the built-in verbosity helpers:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;display.v(&#039;Level 1 verbosity&#039;)
display.vv(&#039;Level 2 verbosity&#039;)
display.vvv(&#039;Level 3 verbosity&#039;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Control verbosity from the command line using &lt;code&gt;-v&lt;/code&gt;, &lt;code&gt;-vv&lt;/code&gt;, or &lt;code&gt;-vvv&lt;/code&gt;. This lets users opt
into more detailed diagnostics without cluttering standard output.&lt;/p&gt;
&lt;h2 id=&quot;Run-time+environment&quot; name=&quot;Run-time+environment&quot;&gt;Run-time environment&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/ansible-actions/rte.png&quot; alt=&quot;rte&quot; /&gt;&lt;/p&gt;
&lt;p&gt;When writing custom action plugins, it is often useful to inspect the execution context;
whether you&#039;re debugging behavior, adapting to playbook settings, or resolving file paths.
Ansible exposes several runtime variables and constants that help you do just that.&lt;/p&gt;
&lt;p&gt;Start by importing the constants module:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import ansible.constants as C&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here are some useful runtime indicators and paths:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/ansible-actions/context.png&quot; alt=&quot;context&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;Execution+Context+Flags&quot; name=&quot;Execution+Context+Flags&quot;&gt;Execution Context Flags&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;self._task.diff&lt;/code&gt;  &lt;br /&gt;Indicates if the task is running in &lt;strong&gt;diff mode&lt;/strong&gt; (&lt;code&gt;--diff&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;self._task.check_mode&lt;/code&gt;  &lt;br /&gt;True if the task is running in &lt;strong&gt;check mode&lt;/strong&gt; (&lt;code&gt;--check&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;self._task.no_log&lt;/code&gt;  &lt;br /&gt;True if the task or play has enabled the &lt;code&gt;no_log&lt;/code&gt; flag.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;self._task.role&lt;/code&gt; &lt;br /&gt;A string with the current role, otherwise None.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Path+Resolution&quot; name=&quot;Path+Resolution&quot;&gt;Path Resolution&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;self._task.get_search_path()&lt;/code&gt;  &lt;br /&gt;Returns the search path used to resolve relative file names.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;C.DEFAULT_ROLES_PATH&lt;/code&gt;  &lt;br /&gt;Default path(s) where Ansible looks for roles.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;C.COLLECTIONS_PATHS&lt;/code&gt;  &lt;br /&gt;Path(s) used to locate collections.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;C.PLAYBOOK_DIR&lt;/code&gt;  &lt;br /&gt;Directory of the current playbook being executed.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Remote+File+Cleanup&quot; name=&quot;Remote+File+Cleanup&quot;&gt;Remote File Cleanup&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;C.DEFAULT_KEEP_REMOTE_FILES&lt;/code&gt;&lt;br /&gt;
Controls whether temporary files on remote hosts are cleaned up.&lt;br /&gt;
You can enable this by setting the environment variable:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;export ANSIBLE_KEEP_REMOTE_FILES=1&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These variables are especially helpful when building plugins that need to behave
differently based on execution mode or when resolving paths dynamically.&lt;/p&gt;
&lt;h2 id=&quot;Accessing+task+parameters&quot; name=&quot;Accessing+task+parameters&quot;&gt;Accessing task parameters&lt;/h2&gt;
&lt;p&gt;Action plugins receive task parameters as a dictionary via &lt;code&gt;self._task.args&lt;/code&gt;. These are the
arguments defined in the playbook for the task invoking your plugin.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;params = self._task.args&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can interact with this dictionary using standard Python methods like &lt;code&gt;.get()&lt;/code&gt; or direct
key access.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: Any parameters containing Jinja2 templates are &lt;strong&gt;rendered before&lt;/strong&gt; the plugin is
executed. This means you’ll receive fully evaluated values, not raw template strings.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This makes it easy to write plugins that behave predictably, without needing to manually
resolve templating logic.&lt;/p&gt;
&lt;h2 id=&quot;More+Task+Context+via+task_vars&quot; name=&quot;More+Task+Context+via+task_vars&quot;&gt;More Task Context via &lt;code&gt;task_vars&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;In addition to parameters passed directly to your action plugin via &lt;code&gt;self._task.args&lt;/code&gt;,
Ansible provides a broader context through the &lt;code&gt;task_vars&lt;/code&gt; dictionary. This contains
&lt;strong&gt;all variables available to the task at runtime&lt;/strong&gt;, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Inventory &amp;amp; Host Variables
&lt;ul&gt;
&lt;li&gt;Variables from &lt;code&gt;host_vars&lt;/code&gt; and &lt;code&gt;group_vars&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;inventory_hostname&lt;/code&gt;, &lt;code&gt;group_names&lt;/code&gt;, &lt;code&gt;ansible_play_hosts&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Facts
&lt;ul&gt;
&lt;li&gt;Gathered facts like &lt;code&gt;ansible_distribution&lt;/code&gt;, &lt;code&gt;ansible_interfaces&lt;/code&gt;, &lt;code&gt;ansible_facts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Custom facts from &lt;code&gt;setup&lt;/code&gt; or &lt;code&gt;set_fact&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Plabook &amp;amp; Role Context
&lt;ul&gt;
&lt;li&gt;Variables defined in &lt;code&gt;vars&lt;/code&gt;, &lt;code&gt;vars_files&lt;/code&gt;, or &lt;code&gt;vars_prompt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Role defaults and vars&lt;/li&gt;
&lt;li&gt;Tags: &lt;code&gt;ansible_run_tags&lt;/code&gt;, &lt;code&gt;ansible_skip_tags&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Execution Flags
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ansible_check_mode&lt;/code&gt;: Whether the task is running in check mode&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ansible_diff_mode&lt;/code&gt;: Whether diff mode is enabled&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ansible_dependent_role_names&lt;/code&gt;: List of dependent roles&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;User-defined Variables
&lt;ul&gt;
&lt;li&gt;Variables passed via &lt;code&gt;--extra-vars&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Any custom values defined earlier in the play&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;From your &lt;code&gt;run&lt;/code&gt; method:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;hostname = task_vars.get(&#039;inventory_hostname&#039;)
os_family = task_vars.get(&#039;ansible_os_family&#039;)
debug_flag = task_vars.get(&#039;my_custom_flag&#039;, False)
raw_value = task_vars.get(&#039;my_inventory_value&#039;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unlike &lt;code&gt;self._task.args&lt;/code&gt;, which contains pre-rendered values, entries in &lt;code&gt;task_vars&lt;/code&gt; may
still include &lt;strong&gt;raw Jinja2 expressions&lt;/strong&gt;, especially when variables are defined in inventory
or playbooks using templating syntax.&lt;/p&gt;
&lt;p&gt;To evaluate these expressions inside your plugin, use the templar:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;my_value = self._templar.template(task_vars.get(&#039;my_inventory_value&#039;))
os_family = self._templar.template(task_vars.get(&#039;ansible_os_family&#039;))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This ensures your plugin works with fully resolved values, allowing it to behave consistently
regardless of how variables were defined.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can also use &lt;code&gt;template()&lt;/code&gt; on dictionaries or lists to resolve nested structures.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;Implementing+check+mode&quot; name=&quot;Implementing+check+mode&quot;&gt;Implementing &lt;em&gt;check mode&lt;/em&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/ansible-modules/dryrun.png&quot; alt=&quot;dry run&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Similar to custom modules, action plugins support &lt;em&gt;check mode&lt;/em&gt;.  When the user passes
the &lt;code&gt;--check&lt;/code&gt; flag, this will set the flag &lt;code&gt;self._task.check_mode&lt;/code&gt; to True.  This
means the playbook is running in &lt;strong&gt;dry-run mode&lt;/strong&gt;.  See &lt;a href=&quot;https://docs.ansible.com/ansible/2.9/user_guide/playbooks_checkmode.html&quot;&gt;Ansible check mode&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When in check mode, the Action plugin must simulate changes without actually applying them to
the system.  Modules that support &lt;em&gt;check mode&lt;/em&gt;, must report what &lt;strong&gt;would&lt;/strong&gt; change or
simply skip execution.&lt;/p&gt;
&lt;p&gt;This is useful for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Validating playbooks before deployment&lt;/li&gt;
&lt;li&gt;Auditing potential changes&lt;/li&gt;
&lt;li&gt;Testing custom modules or logic safely&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Implementing+diff+mode&quot; name=&quot;Implementing+diff+mode&quot;&gt;Implementing &lt;em&gt;diff mode&lt;/em&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/ansible-actions/diff.png&quot; alt=&quot;diff&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Check mode&lt;/em&gt; is often paired with the &lt;code&gt;--diff&lt;/code&gt; command line argument.  This
will set the &lt;code&gt;self._task.diff&lt;/code&gt; to True.  When this is the case, Action
plugins must show the before-and-after changes, especially those that modify files
or configurations.&lt;/p&gt;
&lt;p&gt;A quick example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;
if self._task.diff and result[&#039;changed&#039;]:
  if not &#039;diff&#039; in result: result[&#039;diff&#039;] = list()
  result[&#039;diff&#039;].append({
        &#039;before_header&#039;: &#039;hdr1 (original)&#039;,
        &#039;before&#039;: &#039;before\n&#039;,
        &#039;after_header&#039;: &#039;hdr1 (updated)&#039;,
        &#039;after&#039;: &#039;after\n&#039;,
        &#039;prepared&#039;: &#039;&amp;gt;&amp;gt;&amp;gt; Preparation&#039;,
  })
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example, we are creating a list for the &lt;code&gt;diff&lt;/code&gt; key.  You can return multiple
&lt;code&gt;diff&lt;/code&gt; sets.  If you are only returning one (1) diff set, you can simply return it
directly (without wrapping it inside a list).&lt;/p&gt;
&lt;p&gt;You can return at least &lt;code&gt;before&lt;/code&gt;,&lt;code&gt;after&lt;/code&gt; pair, or a &lt;code&gt;prepared&lt;/code&gt; key.   Returning
all three is possible.  &lt;code&gt;before_header&lt;/code&gt; and &lt;code&gt;after_header&lt;/code&gt; are optional.&lt;/p&gt;
&lt;p&gt;Normally I would return &lt;code&gt;before&lt;/code&gt; and &lt;code&gt;after&lt;/code&gt; for when configuration files
are being changed.  I would use the &lt;code&gt;prepared&lt;/code&gt; key for when we are running
commands on the system.   For example, running &lt;code&gt;mkfs&lt;/code&gt;, &lt;code&gt;lvcreate&lt;/code&gt;, etc.&lt;/p&gt;
&lt;h2 id=&quot;Template+Rendering&quot; name=&quot;Template+Rendering&quot;&gt;Template Rendering&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/ansible-actions/tmpl.png&quot; alt=&quot;template&quot; /&gt;&lt;/p&gt;
&lt;p&gt;When rendering templates from files within an action plugin, the following
pattern ensures correctness and compatibility with Ansible’s templating
engine:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Import the required dependency:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from ansible.template import Templar&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Locate the template file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;template_path = self._task.args.get(&#039;template&#039;)
srcfile = self._find_needle(&#039;templates&#039;, template_path)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This searches for the template in the configured &lt;code&gt;templates/&lt;/code&gt; directories and
resolves the full path.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Adjust the loader&#039;s basedir to support &lt;code&gt;{% include %}&lt;/code&gt; and relative paths:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;old_basedir = self._loader.get_basedir()
self._loader.set_basedir(os.path.dirname(srcfile))&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Render the template:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;with open(srcfile, &#039;r&#039;) as fp:
    template = fp.read()

tp = Templar(loader=self._loader, variables=task_vars)
rendered = tp.template(template)&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Restore the original basedir to avoid side effects:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;self._loader.set_basedir(old_basedir)&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Always reset the loader&#039;s basedir after rendering to prevent unexpected behavior
in subsequent tasks.&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;Templar&lt;/code&gt; directly gives you full control over variable resolution and
template rendering, including support for Ansible-specific filters and syntax.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Calling+Modules+from+Action+Plugins&quot; name=&quot;Calling+Modules+from+Action+Plugins&quot;&gt;Calling Modules from Action Plugins&lt;/h2&gt;
&lt;p&gt;Action plugins often serve as orchestrators, combining logic, templating, and
conditional behavior before delegating actual work to modules. To invoke a module
from within an action plugin, use the &lt;code&gt;_execute_module()&lt;/code&gt; method, which handles
serialization, transport, and return parsing.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/ansible-actions/modcall.png&quot; alt=&quot;calling module&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;Example%3A+Delegating+to+a+Custom+Module&quot; name=&quot;Example%3A+Delegating+to+a+Custom+Module&quot;&gt;Example: Delegating to a Custom Module&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;mod_args = {
    &#039;dest&#039;: dest,
    &#039;files&#039;: &#039;\n&#039;.join(flist),
    &#039;ro_check&#039;: ro_check if ro_check else &#039;&#039;,
}

mod_return = self._execute_module(
    module_name=&#039;mab_prune&#039;,
    module_args=mod_args,
    task_vars=task_vars
)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Key+Concepts&quot; name=&quot;Key+Concepts&quot;&gt;Key Concepts&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;module_name&lt;/code&gt;&lt;/strong&gt;: This should match the name of the module file (without &lt;code&gt;.py&lt;/code&gt;)
and be resolvable via the plugin loader. If you&#039;re calling a module from your own
collection, use the fully qualified name (e.g. &lt;code&gt;my_namespace.my_collection.mab_prune&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;module_args&lt;/code&gt;&lt;/strong&gt;: A dictionary of arguments passed to the module. These should be
flat and serializable.  Avoid nested structures unless your module explicitly supports
them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;task_vars&lt;/code&gt;&lt;/strong&gt;: Pass the current task variables to ensure proper templating and
context resolution. This includes facts, host vars, and role defaults.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Return+Value&quot; name=&quot;Return+Value&quot;&gt;Return Value&lt;/h3&gt;
&lt;p&gt;The result of &lt;code&gt;_execute_module()&lt;/code&gt; is a dictionary that mimics the return from a normal
module execution. It includes keys like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;changed&lt;/code&gt;: Boolean indicating whether the module made changes.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;failed&lt;/code&gt;: Boolean indicating failure.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;msg&lt;/code&gt;: Optional message string.&lt;/li&gt;
&lt;li&gt;Any custom return values defined by your module.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can use this return to control flow, emit diagnostics, or raise errors:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;if mod_return.get(&#039;failed&#039;):
    raise AnsibleActionFail(&quot;Module execution failed: %s&quot; % mod_return.get(&#039;msg&#039;))&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Best+Practices&quot; name=&quot;Best+Practices&quot;&gt;Best Practices&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Avoid side effects before module execution&lt;/strong&gt;: If your plugin modifies state before
calling the module, ensure those changes are reversible in case the module fails.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Respect check mode&lt;/strong&gt;: If your plugin supports check mode, ensure the module you&#039;re
calling does too—or skip execution when &lt;code&gt;self._play_context.check_mode&lt;/code&gt; is &lt;code&gt;True&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use &lt;code&gt;Display.vvv()&lt;/code&gt; for debugging&lt;/strong&gt;: Log the module arguments and return values
at verbosity level 3+ to aid troubleshooting.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Return+values&quot; name=&quot;Return+values&quot;&gt;Return values&lt;/h2&gt;
&lt;p&gt;Action plugins return a dictionary that mirrors the structure of module results.
This ensures consistency across tasks and allows Ansible to interpret outcomes
correctly.&lt;/p&gt;
&lt;h3 id=&quot;Common+Keys&quot; name=&quot;Common+Keys&quot;&gt;Common Keys&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;changed&lt;/code&gt;&lt;/strong&gt; (&lt;code&gt;bool&lt;/code&gt;): Indicates whether the task made changes to the system.
Set this to &lt;code&gt;True&lt;/code&gt; only when a state transition occurred.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;failed&lt;/code&gt;&lt;/strong&gt; (&lt;code&gt;bool&lt;/code&gt;): Signals task failure. If &lt;code&gt;True&lt;/code&gt;, Ansible halts execution
unless &lt;code&gt;ignore_errors&lt;/code&gt; is set.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;msg&lt;/code&gt;&lt;/strong&gt; (&lt;code&gt;str&lt;/code&gt;): A human-readable message describing the outcome or error.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;skipped&lt;/code&gt;&lt;/strong&gt; (&lt;code&gt;bool&lt;/code&gt;): Marks the task as intentionally skipped, often used with
conditional logic.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;invocation&lt;/code&gt;&lt;/strong&gt; (&lt;code&gt;dict&lt;/code&gt;): Automatically populated metadata about how the plugin
was called. Use &lt;code&gt;self._get_invocation(task_vars)&lt;/code&gt; to include it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Custom+Return+Values&quot; name=&quot;Custom+Return+Values&quot;&gt;Custom Return Values&lt;/h3&gt;
&lt;p&gt;You can include additional keys specific to your plugin or module logic:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;return {
    &#039;changed&#039;: True,
    &#039;pruned_files&#039;: pruned,
    &#039;skipped_files&#039;: skipped,
    &#039;msg&#039;: &quot;Pruned %d files&quot; % len(pruned),
    &#039;invocation&#039;: self._get_invocation(task_vars),
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For more common return values see &lt;a href=&quot;https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html&quot;&gt;Common Return values&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;Conclusion&quot; name=&quot;Conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/ansible-actions/ninja.png&quot; alt=&quot;ninja&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Action plugins offer deep control over task execution, making them ideal for
implementing custom logic, enforcing policy, and handling edge cases like
connection loss or remote cleanup. But with that control comes complexity;
especially around templating, context flags, and runtime behavior.&lt;/p&gt;
&lt;p&gt;This article outlined practical patterns for building reliable, maintainable
plugins: from placement and layout to diagnostics, error handling, and module
delegation. The goal is not just flexibility, but &lt;strong&gt;predictability&lt;/strong&gt; -- plugins
that behave consistently across environments, expose their decisions clearly,
and fail gracefully when needed.&lt;/p&gt;
&lt;p&gt;In automation, predictability builds trust. And trust is what lets teams move
fast without breaking things.&lt;/p&gt;</content>
</entry>
<entry>
<title>Ansible Custom Modules</title>
<link href="https://www.0ink.net/posts/2026/2026-05-01-ansible-custom-modules.html"></link>
<id>urn:uuid:061aa4f5-0a2a-6dea-5ac6-61ef5a1f8f66</id>
<updated>2025-09-15T00:00:00+02:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Intro
What is a module
Where to store modules

Inside a Role
...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Intro&quot;&gt;Intro&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#What+is+a+module&quot;&gt;What is a module&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Where+to+store+modules&quot;&gt;Where to store modules&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Inside+a+Role&quot;&gt;Inside a Role&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Inside+a+Collection&quot;&gt;Inside a Collection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Custom+Path+via+ansible.cfg+or+Environment+Variable&quot;&gt;Custom Path via ansible.cfg or Environment Variable&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Writing+modules&quot;&gt;Writing modules&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Recommended%3A+Python&quot;&gt;Recommended: Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Possible+but+Uncommon%3A+Other+Languages&quot;&gt;Possible but Uncommon: Other Languages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Using+Shell+script&quot;&gt;Using Shell script&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Sample+Shell+module&quot;&gt;Sample Shell module&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#JSON+input&quot;&gt;JSON input&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Additional+parameters&quot;&gt;Additional parameters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#check_mode&quot;&gt;check_mode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#ansible_diff&quot;&gt;ansible_diff&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Generating+JSON+output&quot;&gt;Generating JSON output&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Final+thoughts&quot;&gt;Final thoughts&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/ansible_logo2.png&quot; alt=&quot;ansible logo&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;Intro&quot; name=&quot;Intro&quot;&gt;Intro&lt;/h2&gt;
&lt;p&gt;Ansible is at its best when it lets you declare &lt;em&gt;what&lt;/em&gt; a system should look like, not
micromanage &lt;em&gt;how&lt;/em&gt; to get there. Its built-in modules cover most common UNIX configuration
tasks, such as editing files, managing services, installing packages, etc.  &lt;/p&gt;
&lt;p&gt;However, some scenarios demand more than that. For example, when configuring storage subsystems,
orchestrating complex workflows, or dealing with edge cases that require precise command
execution, playbooks alone can get messy fast.&lt;/p&gt;
&lt;p&gt;That’s where custom modules come in. They let you encapsulate logic, handle complexity cleanly,
and keep your roles declarative and maintainable. Whether you&#039;re scripting disk array setup
or wrapping a cloud API call, writing your own module gives you control, clarity, and reusability.&lt;/p&gt;
&lt;h2 id=&quot;What+is+a+module&quot; name=&quot;What+is+a+module&quot;&gt;What is a module&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/ansible-modules/modules.png&quot; alt=&quot;modules&quot; /&gt;&lt;/p&gt;
&lt;p&gt;At the heart of Ansible’s automation engine is the &lt;strong&gt;module&lt;/strong&gt;.  Modules are standalone,
reusable script that performs a specific task. Modules are the building blocks behind
nearly every Ansible action, whether you&#039;re installing packages, managing users, configuring
services, or interacting wiht other resources.&lt;/p&gt;
&lt;p&gt;Each module defines a clear interface: it accepts arguments, executes logic, and returns
structured output (usually as JSON) back to Ansible.
Modules are executed by Ansible on behalf of the user, and can interact with the target system,
an API, or other resources depending on the task.&lt;/p&gt;
&lt;h2 id=&quot;Where+to+store+modules&quot; name=&quot;Where+to+store+modules&quot;&gt;Where to store modules&lt;/h2&gt;
&lt;p&gt;When you write your own Ansible module, you have a few options for where to place it,
depending on how you want to organize and reuse it:&lt;/p&gt;
&lt;h3 id=&quot;Inside+a+Role&quot; name=&quot;Inside+a+Role&quot;&gt;Inside a Role&lt;/h3&gt;
&lt;p&gt;You can store the module in a role’s &lt;code&gt;library/&lt;/code&gt; directory:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;roles/
  my_role/
    library/
      my_module.py&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ansible automatically loads modules from this path when the role is used in a playbook.
This is ideal for role-specific logic that doesn&#039;t need to be shared across multiple projects.&lt;/p&gt;
&lt;h3 id=&quot;Inside+a+Collection&quot; name=&quot;Inside+a+Collection&quot;&gt;Inside a Collection&lt;/h3&gt;
&lt;p&gt;For broader reuse and better packaging, custom modules can live inside a collection:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;collections/
  ansible_collections/
    my_namespace/
      my_collection/
        plugins/
          modules/
            my_module.py&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This structure allows you to version, distribute, and document your module cleanly. It also
integrates with Ansible Galaxy and &lt;code&gt;ansible-galaxy install&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;Custom+Path+via+ansible.cfg+or+Environment+Variable&quot; name=&quot;Custom+Path+via+ansible.cfg+or+Environment+Variable&quot;&gt;Custom Path via &lt;code&gt;ansible.cfg&lt;/code&gt; or Environment Variable&lt;/h3&gt;
&lt;p&gt;If you’re testing or prototyping, you can place your module in any directory and tell Ansible
where to find it by setting:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;[defaults]
library = ./custom_modules&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or by exporting the &lt;code&gt;ANSIBLE_LIBRARY&lt;/code&gt; environment variable:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;export ANSIBLE_LIBRARY=./custom_modules&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When Ansible runs a task that uses your module, it searches these paths in order—starting
with role-local &lt;code&gt;library/&lt;/code&gt;, then collection plugins, then any configured custom paths. Once
found, the module is copied to the target host (unless you’ve delegated execution to
&lt;code&gt;localhost&lt;/code&gt;) and executed with arguments passed via a temporary JSON file.&lt;/p&gt;
&lt;h2 id=&quot;Writing+modules&quot; name=&quot;Writing+modules&quot;&gt;Writing modules&lt;/h2&gt;
&lt;p&gt;When writing custom Ansible modules, &lt;strong&gt;Python&lt;/strong&gt; is the primary and officially supported
language. It&#039;s tightly integrated with Ansible&#039;s internals and benefits from the
&lt;code&gt;AnsibleModule&lt;/code&gt; helper class, which simplifies argument parsing, error handling, and
JSON output.&lt;/p&gt;
&lt;p&gt;That said, &lt;strong&gt;other languages can technically be used&lt;/strong&gt;, but with caveats:&lt;/p&gt;
&lt;h3 id=&quot;Recommended%3A+Python&quot; name=&quot;Recommended%3A+Python&quot;&gt;Recommended: Python&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Full support via &lt;code&gt;ansible.module_utils.basic.AnsibleModule&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Access to Ansible’s plugin APIs and utilities&lt;/li&gt;
&lt;li&gt;Clean integration with roles, collections, and documentation&lt;/li&gt;
&lt;li&gt;Easy to test and debug using &lt;code&gt;ansible-test&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Possible+but+Uncommon%3A+Other+Languages&quot; name=&quot;Possible+but+Uncommon%3A+Other+Languages&quot;&gt;Possible but Uncommon: Other Languages&lt;/h3&gt;
&lt;p&gt;You can write modules in &lt;strong&gt;any language&lt;/strong&gt; that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Can read JSON input from a temporary file.&lt;/li&gt;
&lt;li&gt;Can write JSON output to &lt;code&gt;stdout&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Can exit with a proper return code&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Using+Shell+script&quot; name=&quot;Using+Shell+script&quot;&gt;Using Shell script&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/ansible-modules/full_colored_dark_sm.png&quot; alt=&quot;bash logo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Using Shell over the recommended Python for Ansible modules may be a better
fit for:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Re-using existing Shell scripts &lt;br /&gt;If you&#039;ve built up a library of reliable shell scripts over time, reusing that logic
inside Ansible modules saves effort and reduces risk. You don&#039;t need to reimplement or
debug complex workflows in Python when they already work in shell.&lt;/li&gt;
&lt;li&gt;Target Systems Don’t Have Python &lt;br /&gt;Minimalist systems—like Alpine Linux, embedded devices, or hardened containers—often lack
Python by default. Shell is universally available, making it the most portable option for
modules that need to run directly on the managed node.&lt;/li&gt;
&lt;li&gt;Closer to the System &lt;br /&gt;Shell is ideal for tasks that interact directly with system utilities: &lt;code&gt;nft&lt;/code&gt;, &lt;code&gt;ip&lt;/code&gt;, &lt;code&gt;vgcreate&lt;/code&gt;,
&lt;code&gt;mount&lt;/code&gt;, &lt;code&gt;systemctl&lt;/code&gt;, and so on. These tools are designed to be used from the shell, and
wrapping them in Python often adds unnecessary complexity.&lt;/li&gt;
&lt;li&gt;Fast Prototyping &lt;br /&gt;Shell scripts are quick to write, test, and iterate. If you’re building a module for a
specific role or environment, shell lets you move fast without worrying about Python
packaging, dependencies, or module APIs.&lt;/li&gt;
&lt;li&gt;Minimal Dependencies &lt;br /&gt;Shell modules don&#039;t require external libraries or Python environments. Tools like &lt;code&gt;jq&lt;/code&gt; can
help with, but that can be optional for simple use cases.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Shell isn&#039;t ideal for everything—error handling, argument validation, and testing are more
limited than in Python, but for infrastructure tasks that are already shell-native, it&#039;s often
the most efficient and maintainable choice.&lt;/p&gt;
&lt;h2 id=&quot;Sample+Shell+module&quot; name=&quot;Sample+Shell+module&quot;&gt;Sample Shell module&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/sh
. &quot;$1&quot;

# something happens here

jq -Mn &#039;$ARGS.named&#039; \
    --argjson changed false \
    --arg msg &quot;Hello world&quot; \
    --argjson ansible_facts &quot;$(jq -n &#039;$ARGS.named&#039; \
      --arg uptime &quot;$(uptime)&quot;
    )&quot; \
    --arg input &quot;$(cat &quot;$1&quot;)&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a sample skeleton file, which does nothing but is a complete Ansible module.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;. &quot;$1&quot;&lt;/code&gt; &lt;br /&gt;Parses the input parameters.  By default, Ansible assume legacy mode, so we can just
source the input file as it will contain key=value pairs. &lt;br /&gt;This means that simple flat parametes can be passed directly.  Use &lt;code&gt;WANT_JSON&lt;/code&gt; for
more complex structures, but this adds a dependancy on &lt;code&gt;jq&lt;/code&gt; for the JSON parsing.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;jq ...&lt;/code&gt; &lt;br /&gt;This generates a JSON response.&lt;br /&gt;While we are using &lt;code&gt;jq&lt;/code&gt; to generate the JSON response, this is not necessary.  As
long as you output valid JSON, you can do it simply with shell code, for example:
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;echo &#039;{ &quot;changed&quot;: false }&#039;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The response should at least contain:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;changed&lt;/code&gt; : boolean, &lt;code&gt;true&lt;/code&gt; or &lt;code&gt;false&lt;/code&gt; depending on the outcome of the script.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the example, we are also returning:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;msg&lt;/code&gt; : Most modules seem to return this explaining what happened.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ansible_facts&lt;/code&gt; : Used for &lt;a href=&quot;https://docs.ansible.com/ansible/latest/dev_guide/developing_modules_general.html#creating-an-info-or-a-facts-module&quot;&gt;facts modules&lt;/a&gt; to return information on the host.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Other return values:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;failed&lt;/code&gt; : boolean, &lt;br /&gt;return &lt;code&gt;true&lt;/code&gt; if the task failed.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;skipped&lt;/code&gt;: boolean,
return &lt;code&gt;true&lt;/code&gt; if the task was skipped.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My convention is that &lt;code&gt;failed&lt;/code&gt; is when there was an error, whereas &lt;code&gt;skipped&lt;/code&gt; is more for
when the task can not be executed.&lt;/p&gt;
&lt;p&gt;For example, typically I use &lt;code&gt;skipped&lt;/code&gt; when implementing &lt;em&gt;check mode&lt;/em&gt;.  Very often
a play would have a task that installs package dependancies.  Obviously if we
are running in &lt;em&gt;check mode&lt;/em&gt;, the package would not be installed.  Rather than
fail the play by returning &lt;code&gt;failed: true&lt;/code&gt; as normally would do, if the
module is running in &lt;em&gt;check mode&lt;/em&gt; I would return &lt;code&gt;skipped&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;JSON+input&quot; name=&quot;JSON+input&quot;&gt;JSON input&lt;/h2&gt;
&lt;p&gt;For some scenarios, you may need to pass more complex paramters.  You can
easily achieve this by declaring your custom module to want JSON:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/sh
# WANT_JSON

echo &#039;{&quot;changed&quot;: false}&#039;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By having the string &lt;code&gt;WANT_JSON&lt;/code&gt; around the start of the file, this tells
Ansible that this module accepts JSON input.&lt;/p&gt;
&lt;h2 id=&quot;Additional+parameters&quot; name=&quot;Additional+parameters&quot;&gt;Additional parameters&lt;/h2&gt;
&lt;p&gt;In addition to the parameters in the playbook, the following parameters
are send to the module:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;_ansible_check_mode&lt;/code&gt; : boolean, if true, &lt;em&gt;check mode&lt;/em&gt; is being used (cli option: &lt;code&gt;--check&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_ansible_diff&lt;/code&gt; : boolean, if true &lt;em&gt;diff mode&lt;/em&gt; is being used (cli option: &lt;code&gt;--diff&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_ansible_verbosity&lt;/code&gt; : int,  cli options: &lt;code&gt;-v&lt;/code&gt;, &lt;code&gt;-vv&lt;/code&gt; or &lt;code&gt;-vvv&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_ansible_keep_remote_files&lt;/code&gt;: boolean, environment &lt;code&gt;ANSIBLE_KEEP_REMOTE_FILES=1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_ansible_version&lt;/code&gt;: str, example: &lt;code&gt;2.18.8&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_ansible_no_log&lt;/code&gt; : boolean&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_ansible_debug&lt;/code&gt; : boolean&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_ansible_version&lt;/code&gt;: str, example: &lt;code&gt;2.18.8&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_ansible_module_name&lt;/code&gt;: str &lt;/li&gt;
&lt;li&gt;&lt;code&gt;_ansible_syslog_facility&lt;/code&gt; : str&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_ansible_selinux_special_fs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_ansible_string_conversion_action&lt;/code&gt; : enum&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_ansible_socket&lt;/code&gt; : null&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_ansible_shell_executable&lt;/code&gt; : str&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_ansible_tmpdir&lt;/code&gt; : str&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_ansible_remote_tmp&lt;/code&gt; : str&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_ansible_ignore_unknown_opts&lt;/code&gt; : boolean&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;check_mode&quot; name=&quot;check_mode&quot;&gt;check_mode&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/ansible-modules/dryrun.png&quot; alt=&quot;dry run&quot; /&gt;&lt;/p&gt;
&lt;p&gt;When modules are passed the parameter &lt;code&gt;_ansible_check_mode=True&lt;/code&gt;, this means the playbook
is running in &lt;strong&gt;dry-run mode&lt;/strong&gt;.  See &lt;a href=&quot;https://docs.ansible.com/ansible/2.9/user_guide/playbooks_checkmode.html&quot;&gt;Ansible check mode&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When in check mode, the module must simulate changes without actually applying them to
the system.  Modules that support &lt;em&gt;check mode&lt;/em&gt;, must report what &lt;strong&gt;would&lt;/strong&gt; change or
simply skip execution.&lt;/p&gt;
&lt;p&gt;This is useful for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Validating playbooks before deployment&lt;/li&gt;
&lt;li&gt;Auditing potential changes&lt;/li&gt;
&lt;li&gt;Testing custom modules or logic safely&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;ansible_diff&quot; name=&quot;ansible_diff&quot;&gt;ansible_diff&lt;/h2&gt;
&lt;p&gt;When modules are passed the parameter &lt;code&gt;_ansible_diff=True&lt;/code&gt;, it must show the
before-and-after changes, especially those that modify files or configurations.&lt;/p&gt;
&lt;p&gt;Typically, this is combined with &lt;em&gt;check mode&lt;/em&gt;  to preview changes without applying them.&lt;/p&gt;
&lt;p&gt;To support this, you must include a &lt;code&gt;diff&lt;/code&gt; key in the return JSON object:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;diff: [{
    &quot;before_header&quot;: &quot;/etc/my_config.ini (original)&quot;,
    &quot;before&quot;: &quot;line1 = old content\n&quot;,
    &quot;after_header&quot;: &quot;/etc/my_config.ini (original)&quot;,
    &quot;after&quot;: &quot;line1 = new content\n&quot;,
    &quot;prepared&quot;: &quot;&amp;gt;&amp;gt;&amp;gt; reload apache&quot;,
}]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Not shown in the example, but you must return &lt;code&gt;&quot;changed&quot;: true&lt;/code&gt;, otherwise the diff
sets will &lt;strong&gt;not&lt;/strong&gt; be processed.&lt;/p&gt;
&lt;p&gt;In this example, we are creating a list for the &lt;code&gt;diff&lt;/code&gt; key.  You can return multiple
&lt;code&gt;diff&lt;/code&gt; sets.  If you are only returning one (1) diff set, you can simply return it
directly (without wrapping it inside a list).&lt;/p&gt;
&lt;p&gt;You can return at least &lt;code&gt;before&lt;/code&gt;,&lt;code&gt;after&lt;/code&gt; pair, or a &lt;code&gt;prepared&lt;/code&gt; key.   Returning
all three is possible.  &lt;code&gt;before_header&lt;/code&gt; and &lt;code&gt;after_header&lt;/code&gt; are optional.&lt;/p&gt;
&lt;p&gt;Normally I would return &lt;code&gt;before&lt;/code&gt; and &lt;code&gt;after&lt;/code&gt; for when configuration files
are being changed.  I would use the &lt;code&gt;prepared&lt;/code&gt; key for when we are running
commands on the system.   For example, running &lt;code&gt;mkfs&lt;/code&gt;, &lt;code&gt;lvcreate&lt;/code&gt;, etc.&lt;/p&gt;
&lt;h2 id=&quot;Generating+JSON+output&quot; name=&quot;Generating+JSON+output&quot;&gt;Generating JSON output&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/ansible-modules/json-b.png&quot; alt=&quot;json logo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;While you can use &lt;code&gt;echo&lt;/code&gt; statements to generate the JSON output, to simplify things
you can use the &lt;code&gt;jq&lt;/code&gt; command instead.  Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jq -Mn &#039;$ARGS.named&#039; \
    --argjson changed false \
    --arg msg &quot;Hello world&quot; \
    --argjson ansible_facts &quot;$(jq -n &#039;$ARGS.named&#039; \
      --arg uptime &quot;$(uptime)&quot;
    )&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example we are using:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-M&lt;/code&gt; or &lt;code&gt;--monochrome-output&lt;/code&gt; : This prevents &lt;code&gt;jq&lt;/code&gt; to use ANSI escape sequences
to colorize its output.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-n&lt;/code&gt; or &lt;code&gt;--null-input&lt;/code&gt; : Skip reading any input&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&#039;$ARGS.named&lt;/code&gt;&#039; : outputs the arguments specified in the commnad line&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--argjson key value&lt;/code&gt; : define the &lt;em&gt;key&lt;/em&gt; as a valid json &lt;em&gt;value&lt;/em&gt;.  This can be
a full json structure, but most commonly is used to define booleans or numbers.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--arg key value&lt;/code&gt; : define the &lt;em&gt;key&lt;/em&gt; as the string &lt;em&gt;value&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Final+thoughts&quot; name=&quot;Final+thoughts&quot;&gt;Final thoughts&lt;/h2&gt;
&lt;p&gt;While Python remains the default language for &lt;a href=&quot;https://docs.ansible.com/ansible/latest/dev_guide/developing_modules_general.html&quot;&gt;Ansible module development&lt;/a&gt;, shell
scripting offers a pragmatic alternative, especially in environments where Python isn&#039;t readily
available or where existing shell logic is already battle-tested. By understanding how
Ansible passes arguments, handles check and diff modes, and expects structured output, you can
write shell-based modules that integrate cleanly into your automation workflows without
sacrificing clarity or control.&lt;/p&gt;
&lt;p&gt;Whether you&#039;re managing minimalist systems, reusing hardened scripts, or simply prefer the
transparency of shell, custom modules give you the flexibility to tailor Ansible to your
infrastructure, not the other way around.&lt;/p&gt;</content>
</entry>
<entry>
<title>Using sphinx for doc generation</title>
<link href="https://www.0ink.net/posts/2026/2026-04-15-py-sphinx.html"></link>
<id>urn:uuid:edc1e19e-e885-4778-9d47-f09510fceff9</id>
<updated>2025-07-11T00:00:00+02:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Introduction
Basic functionality
Sample Usage

docs/Makefile and docs/make.bat
...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Introduction&quot;&gt;Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Basic+functionality&quot;&gt;Basic functionality&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Sample+Usage&quot;&gt;Sample Usage&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#docs%2FMakefile+and+docs%2Fmake.bat&quot;&gt;docs/Makefile and docs/make.bat&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#docs%2FMakefile&quot;&gt;docs/Makefile&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#docs%2Fmake.bat&quot;&gt;docs/make.bat&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#docs%2Fconf.py&quot;&gt;docs/conf.py&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#autodoc2&quot;&gt;autodoc2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#myst_parser&quot;&gt;myst_parser&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#sphinx.ext.doctest&quot;&gt;sphinx.ext.doctest&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#docs%2Findex.md&quot;&gt;docs/index.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#docs%2Fcli.md&quot;&gt;docs/cli.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Docstrings&quot;&gt;Docstrings&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Generating+documentation&quot;&gt;Generating documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Publish+to+gh-pages&quot;&gt;Publish to gh-pages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/sphinx/sphinx-logo.png&quot; alt=&quot;logo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I have started using &lt;a href=&quot;https://www.sphinx-doc.org/en/master/&quot;&gt;sphinx&lt;/a&gt; for documention generation.&lt;/p&gt;
&lt;p&gt;I do find &lt;a href=&quot;https://www.sphinx-doc.org/en/master/&quot;&gt;sphinx&lt;/a&gt; quite &lt;em&gt;prickly&lt;/em&gt;
with the slightest mistake breaking things and error messages that are very
&lt;em&gt;uninformative&lt;/em&gt; or &lt;em&gt;confusing&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Nevertheless, it is quite close to feature complete. &lt;/p&gt;
&lt;h2 id=&quot;Introduction&quot; name=&quot;Introduction&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.sphinx-doc.org/en/master/&quot;&gt;Sphinx&lt;/a&gt; is a documentation generator written and used by the
Python comunity.  It is written in Python, but it can be also used
to document projects written in other languages.&lt;/p&gt;
&lt;p&gt;By default, it uses &lt;a href=&quot;https://en.wikipedia.org/wiki/ReStructuredText&quot;&gt;reSturcutredText&lt;/a&gt; but it there are extension
to allow for &lt;a href=&quot;https://myst-parser.readthedocs.io/en/v0.15.1/sphinx/intro.html&quot;&gt;Markdown&lt;/a&gt; compatibility.&lt;/p&gt;
&lt;p&gt;It can generate output in multiple formats.  Among others it supports:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;HTML (in different configurations)&lt;/li&gt;
&lt;li&gt;EPub&lt;/li&gt;
&lt;li&gt;Man pages&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/sphinx/workflow-lo.png&quot; alt=&quot;sphinx workflow&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;Basic+functionality&quot; name=&quot;Basic+functionality&quot;&gt;Basic functionality&lt;/h2&gt;
&lt;p&gt;If you are using the default functionality you only really need the &lt;code&gt;sphinx&lt;/code&gt;
Python package.&lt;/p&gt;
&lt;p&gt;In this scenario, you must write all your documentation in &lt;a href=&quot;https://en.wikipedia.org/wiki/ReStructuredText&quot;&gt;reStructuredText&lt;/a&gt;
and use &lt;a href=&quot;https://www.sphinx-doc.org/en/master/man/sphinx-apidoc.html&quot;&gt;apidoc extension&lt;/a&gt;.  Note, there is also an extension named
&lt;a href=&quot;https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html&quot;&gt;autodoc&lt;/a&gt;.  This is &lt;em&gt;NOT&lt;/em&gt; the one you want, as it requires that you
manually write &lt;a href=&quot;https://en.wikipedia.org/wiki/ReStructuredText&quot;&gt;reStructuredText&lt;/a&gt; files pointing to your source code.  The
&lt;a href=&quot;https://www.sphinx-doc.org/en/master/man/sphinx-apidoc.html&quot;&gt;apidoc&lt;/a&gt; extension, makes use of the &lt;a href=&quot;https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html&quot;&gt;autodoc&lt;/a&gt; extension and
creates any needed &lt;a href=&quot;https://en.wikipedia.org/wiki/ReStructuredText&quot;&gt;reStructuredText&lt;/a&gt; files that may be needed.  The
limitation of &lt;a href=&quot;https://www.sphinx-doc.org/en/master/man/sphinx-apidoc.html&quot;&gt;apidoc&lt;/a&gt; is that it is hard-coded to always generated
&lt;a href=&quot;https://en.wikipedia.org/wiki/ReStructuredText&quot;&gt;reStructuredText&lt;/a&gt;, so it can not be used for &lt;a href=&quot;https://myst-parser.readthedocs.io/en/v0.15.1/sphinx/intro.html&quot;&gt;Markdown&lt;/a&gt; documentation.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/sphinx/rstdemo.png&quot; alt=&quot;rst demo&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;Sample+Usage&quot; name=&quot;Sample+Usage&quot;&gt;Sample Usage&lt;/h2&gt;
&lt;p&gt;As mentioned earlier, &lt;a href=&quot;https://www.sphinx-doc.org/en/master/&quot;&gt;sphinx&lt;/a&gt; is quite &lt;em&gt;prickly&lt;/em&gt;, so this is the configuration
I arrived at after a lot of trial and error.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.sphinx-doc.org/en/master/&quot;&gt;sphinx&lt;/a&gt; has a &lt;code&gt;sphinx-quickstart&lt;/code&gt; command which is meant to set things up
for you.  I prefer to do this manually.&lt;/p&gt;
&lt;p&gt;First of all, I assume that you have a project directory with a structure like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/sphinx/pkgs.png&quot; alt=&quot;pkgs&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;your_package
│── your_package/                # Main package directory
│   ├── __init__.py              # Makes it a Python package
│   ├── module.py                # Your actual Python code
│── setup.py                      # Installation configuration
│── requirements.txt              # Optional: Dependencies list
│── README.md                     # Optional: Project info&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To this I add a directory &lt;code&gt;docs&lt;/code&gt; so the structure looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;your_package
│── docs/                        # Sphinx documentation directory
│── your_package/                # Main package directory
│   ├── __init__.py              # Makes it a Python package
│   ├── module.py                # Your actual Python code
│── setup.py                      # Installation configuration
│── requirements.txt              # Optional: Dependencies list
│── README.md                     # Optional: Project info&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For document generation we require the following Python packages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;docutils : not really needed as it is a dependancy for sphinx&lt;/li&gt;
&lt;li&gt;sphinx : main documentation generator&lt;/li&gt;
&lt;li&gt;sphinx-autodoc2 : automatic documentation generation from source code&lt;/li&gt;
&lt;li&gt;myst-parser : &lt;a href=&quot;https://myst-parser.readthedocs.io/en/v0.15.1/sphinx/intro.html&quot;&gt;Markdown&lt;/a&gt; parser for &lt;a href=&quot;https://www.sphinx-doc.org/en/master/&quot;&gt;sphinx&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;linkify-it-py : Dependancy for myst, adding convenient auto URL conversion.&lt;/li&gt;
&lt;li&gt;sphinx-argparse : Used  to document command line parsers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For convenience, I add this to my &lt;code&gt;docs/requirements.txt&lt;/code&gt; file, since these
are dependancies for doc generation and not for the package itself.&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;docs&lt;/code&gt;, I create the files: &lt;code&gt;Makefile&lt;/code&gt;, &lt;code&gt;conf.py&lt;/code&gt;, &lt;code&gt;index.md&lt;/code&gt;, &lt;code&gt;make.bat&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;docs%2FMakefile+and+docs%2Fmake.bat&quot; name=&quot;docs%2FMakefile+and+docs%2Fmake.bat&quot;&gt;docs/Makefile and docs/make.bat&lt;/h3&gt;
&lt;p&gt;The main items to pay attention in these two files are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SOURCEDIR = .&lt;/code&gt; &lt;br /&gt;This tells &lt;a href=&quot;https://www.sphinx-doc.org/en/master/&quot;&gt;sphinx&lt;/a&gt; that current directory (&lt;code&gt;docs&lt;/code&gt;) is where
the documentation source files are.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;BUILDDIR = _build&lt;/code&gt; &lt;br /&gt;Sets-up the direction &lt;code&gt;_build&lt;/code&gt; to be the output directory.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It is a good idea to add &lt;code&gt;_build&lt;/code&gt; to the &lt;code&gt;.gitignore&lt;/code&gt; file in &lt;code&gt;docs&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&quot;docs%2FMakefile&quot; name=&quot;docs%2FMakefile&quot;&gt;docs/Makefile&lt;/h4&gt;
&lt;script src=&quot;https://tortugalabs.github.io/embed-like-gist/embed.js?style=github&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showCopy=on&amp;amp;fetchFromJsDelivr=off&amp;amp;target=https://github.com/TortugaLabs/mypielib/blob/main/docs/Makefile&quot;&gt;&lt;/script&gt;
&lt;h4 id=&quot;docs%2Fmake.bat&quot; name=&quot;docs%2Fmake.bat&quot;&gt;docs/make.bat&lt;/h4&gt;
&lt;script src=&quot;https://tortugalabs.github.io/embed-like-gist/embed.js?style=github&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showCopy=on&amp;amp;fetchFromJsDelivr=off&amp;amp;target=https://github.com/TortugaLabs/mypielib/blob/main/docs/mk.bat&quot;&gt;&lt;/script&gt;
&lt;h3 id=&quot;docs%2Fconf.py&quot; name=&quot;docs%2Fconf.py&quot;&gt;docs/conf.py&lt;/h3&gt;
&lt;p&gt;This is where most of the &lt;a href=&quot;https://www.sphinx-doc.org/en/master/&quot;&gt;sphinx&lt;/a&gt; relevant settings reside.  This
is what I am using:&lt;/p&gt;
&lt;script src=&quot;https://tortugalabs.github.io/embed-like-gist/embed.js?style=github&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showCopy=on&amp;amp;fetchFromJsDelivr=off&amp;amp;target=https://github.com/TortugaLabs/mypielib/blob/main/docs/conf.py&quot;&gt;&lt;/script&gt;
&lt;p&gt;This config file is loading version information from the module itself.
Other meta-data can be set here.&lt;/p&gt;
&lt;p&gt;The following extensions are enabled:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;autodoc2 : generate documentation from doc strings&lt;/li&gt;
&lt;li&gt;myst_parser : Markdown&lt;/li&gt;
&lt;li&gt;sphinxarg.ext : Generate documentation from argument parser&lt;/li&gt;
&lt;li&gt;sphinx.ext.doctest : Enable doctest unit tests.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/sphinx/myst-sphinx.png&quot; alt=&quot;myst+sphinx&quot; /&gt;&lt;/p&gt;
&lt;h4 id=&quot;autodoc2&quot; name=&quot;autodoc2&quot;&gt;autodoc2&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;autodoc2_packages&lt;/code&gt; : configures what source directories to find doc strings&lt;/li&gt;
&lt;li&gt;&lt;code&gt;autodoc2_render_plugin = &#039;myst&#039;&lt;/code&gt; : We use markdown&lt;/li&gt;
&lt;li&gt;&lt;code&gt;autodoc2_sort_names = True&lt;/code&gt; : keeps names sorted.  Otherwise the order is random.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;autodoc2_hidden_objects = {&#039;inherited&#039;,&#039;private&#039;}&lt;/code&gt; : hides inherited classes and
names that begin with a &lt;em&gt;single&lt;/em&gt; underscore.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;myst_parser&quot; name=&quot;myst_parser&quot;&gt;myst_parser&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;myst_enable_extensions&lt;/code&gt; : Enable extensions:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;fieldlist&lt;/code&gt; : Enables the use of &lt;code&gt;:key value:&lt;/code&gt; in doc strings&lt;/li&gt;
&lt;li&gt;&lt;code&gt;linkify&lt;/code&gt; : Converts raw URLs into links&lt;/li&gt;
&lt;li&gt;&lt;code&gt;substitution&lt;/code&gt; : Lets you configure Jinja2 style substitutions.  See: &lt;code&gt;myst_substitutions&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;strikethrough&lt;/code&gt; : Enables &lt;code&gt;~~&lt;/code&gt; markup.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;sphinx.ext.doctest&quot; name=&quot;sphinx.ext.doctest&quot;&gt;sphinx.ext.doctest&lt;/h4&gt;
&lt;p&gt;Enables unit tests in documentation.  Use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;doctest_global_setup&lt;/code&gt; and&lt;/li&gt;
&lt;li&gt;&lt;code&gt;doctest_global_cleanup&lt;/code&gt; to configure the execution environment&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;docs%2Findex.md&quot; name=&quot;docs%2Findex.md&quot;&gt;docs/index.md&lt;/h3&gt;
&lt;p&gt;This is the main page for the generated HTML documentation.  Example
contents:&lt;/p&gt;
&lt;script src=&quot;https://tortugalabs.github.io/embed-like-gist/embed.js?style=github&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showCopy=on&amp;amp;fetchFromJsDelivr=on&amp;amp;target=https://github.com/TortugaLabs/mypielib/blob/main/docs/index.md&quot;&gt;&lt;/script&gt;
&lt;p&gt;In this example, we are using Jinja2 style substitutions.  These are defined
in the &lt;code&gt;conf.py&lt;/code&gt; file in the &lt;code&gt;myst_substituions&lt;/code&gt; dictionary.  Then, they
are refered in the document as &lt;code&gt;{{ key }}&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;{toctree}&lt;/code&gt; structure is used to create a navigation tree.&lt;/p&gt;
&lt;p&gt;The files listed there are the entry points for the documentation.
These files are created as &lt;a href=&quot;https://myst-parser.readthedocs.io/en/v0.15.1/sphinx/intro.html&quot;&gt;Markdown&lt;/a&gt; or possibly &lt;a href=&quot;https://en.wikipedia.org/wiki/ReStructuredText&quot;&gt;reStructuredText&lt;/a&gt;
and reference here.&lt;/p&gt;
&lt;p&gt;Important to note is the &lt;code&gt;apidocs/index&lt;/code&gt;.  This file is generated &lt;strong&gt;automatically&lt;/strong&gt;
by the &lt;code&gt;autodoc2&lt;/code&gt; extension.&lt;/p&gt;
&lt;p&gt;In this example, we are also referencing a file &lt;code&gt;cli.md&lt;/code&gt;.  We are using this
to document command line arguments.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/sphinx/markdown.png&quot; alt=&quot;markdown&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;docs%2Fcli.md&quot; name=&quot;docs%2Fcli.md&quot;&gt;docs/cli.md&lt;/h3&gt;
&lt;p&gt;This file is used by &lt;code&gt;index.md&lt;/code&gt; and is used in the command line documentation.&lt;/p&gt;
&lt;p&gt;Example content:&lt;/p&gt;
&lt;script src=&quot;https://tortugalabs.github.io/embed-like-gist/embed.js?style=github&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showCopy=on&amp;amp;fetchFromJsDelivr=off&amp;amp;target=https://github.com/TortugaLabs/mypielib/blob/main/docs/cli.md&quot;&gt;&lt;/script&gt;
&lt;p&gt;See &lt;a href=&quot;https://sphinx-argparse.readthedocs.io/en/latest/usage.html&quot;&gt;argparse usage&lt;/a&gt; on more details on how argparse is used.&lt;/p&gt;
&lt;h3 id=&quot;Docstrings&quot; name=&quot;Docstrings&quot;&gt;Docstrings&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/sphinx/docstrings-lo.png&quot; alt=&quot;docstrings&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In your source code you can then use docstrings.  Here is an example:&lt;/p&gt;
&lt;script src=&quot;https://tortugalabs.github.io/embed-like-gist/embed.js?style=github&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showCopy=on&amp;amp;fetchFromJsDelivr=off&amp;amp;target=https://github.com/TortugaLabs/mypielib/blob/main/mypielib/cidr_to_netmask.py&quot;&gt;&lt;/script&gt;
&lt;p&gt;The example uses:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;`:param type varname: to document parameters to the function&lt;/li&gt;
&lt;li&gt;`:returns: to document the return value&lt;/li&gt;
&lt;li&gt;It makes use of &lt;code&gt;doctest&lt;/code&gt; to document examples and/or execute tests.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Generating+documentation&quot; name=&quot;Generating+documentation&quot;&gt;Generating documentation&lt;/h2&gt;
&lt;p&gt;In the &lt;code&gt;docs&lt;/code&gt; directory you can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;make html&lt;/code&gt; : to generate HTML documentation.  This will be placed in
&lt;code&gt;_build/html&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;make man&lt;/code&gt; : To generate a man page.  This will be placed in &lt;code&gt;_build/man&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/sphinx/runmake-lo.png&quot; alt=&quot;run make&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;Publish+to+gh-pages&quot; name=&quot;Publish+to+gh-pages&quot;&gt;Publish to gh-pages&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/sphinx/gh-pages.png&quot; alt=&quot;github pages&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If your project is hosted on [github][gh], you can publish the documentation directly
to &lt;a href=&quot;https://pages.github.com/&quot;&gt;github-pages&lt;/a&gt; using a [github-action][action].&lt;/p&gt;
&lt;p&gt;Enable this by navigating to your project settings.  Click on &lt;code&gt;Pages&lt;/code&gt; on the left sidebar.
Change the &lt;strong&gt;Source&lt;/strong&gt; to &lt;code&gt;GitHub Actions&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/sphinx/gh-actions.png&quot; alt=&quot;github actions&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Create the following workflow in &lt;code&gt;.github/workflows/gh-pages.yml&lt;/code&gt;.&lt;/p&gt;
&lt;script src=&quot;https://tortugalabs.github.io/embed-like-gist/embed.js?style=github&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showCopy=on&amp;amp;fetchFromJsDelivr=off&amp;amp;target=https://github.com/TortugaLabs/mypielib/blob/main/.github/workflows/gh-pages.yml&quot;&gt;&lt;/script&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;on:&lt;/code&gt; section: what triggers the workflow to run
&lt;ul&gt;
&lt;li&gt;pushing to the default branch&lt;/li&gt;
&lt;li&gt;&lt;code&gt;workflow_dispatch&lt;/code&gt; let&#039;s you manually run the workflow from the Web UI&#039;s actions tab.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Jobs:
&lt;ul&gt;
&lt;li&gt;build: Use sphinx to generate documentation
&lt;ul&gt;
&lt;li&gt;Checkout : Uses &lt;code&gt;fetch-depth: 0&lt;/code&gt; to make sure we get tag data.&lt;/li&gt;
&lt;li&gt;Setup Python : sets up python, enabling &lt;code&gt;pip&lt;/code&gt; caching.&lt;/li&gt;
&lt;li&gt;Install dependancies : uses the &lt;code&gt;docs/requirements.txt&lt;/code&gt; file.&lt;/li&gt;
&lt;li&gt;Generate sphinx documentation : Run sphinx with the HTML target&lt;/li&gt;
&lt;li&gt;Afterwards, an artifact containing the web site is generated&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;deploy: This job is only run from the default branch and actually
deploys the web site to &lt;a href=&quot;https://pages.github.com/&quot;&gt;github-pages&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After the first succesful run, clock on the gear widget next to your &lt;strong&gt;About&lt;/strong&gt; repository web part.
Click &lt;em&gt;Use your GitHub Pages website&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;Conclusion&quot; name=&quot;Conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This article covers using &lt;a href=&quot;https://www.sphinx-doc.org/en/master/&quot;&gt;sphinx&lt;/a&gt; as a document generator for Python projects and
the eventual publishing of such documentation to &lt;a href=&quot;https://pages.github.com/&quot;&gt;github pages&lt;/a&gt;.  This particular
tutorial is geared towards using &lt;a href=&quot;https://myst-parser.readthedocs.io/en/v0.15.1/sphinx/intro.html&quot;&gt;Markdown&lt;/a&gt; as the markup dialect instead of
&lt;a href=&quot;https://en.wikipedia.org/wiki/ReStructuredText&quot;&gt;reStructured Text&lt;/a&gt; as I personally find &lt;a href=&quot;https://myst-parser.readthedocs.io/en/v0.15.1/sphinx/intro.html&quot;&gt;Markdown&lt;/a&gt; easier to write.&lt;/p&gt;</content>
</entry>
<entry>
<title>javascript snippets</title>
<link href="https://www.0ink.net/posts/2026/2026-04-01-javascript-snippets.html"></link>
<id>urn:uuid:6d72e2d6-4dff-90d6-e31f-088d2f96c5ab</id>
<updated>2025-05-12T00:00:00+02:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Compute how an element is hidden
Iterate over LI elements
Adding/removing classes to/from elements
Get current date as YYYY-MM-DD
escape HTML characters
...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Compute+how+an+element+is+hidden&quot;&gt;Compute how an element is hidden&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Iterate+over+LI+elements&quot;&gt;Iterate over LI elements&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Adding%2Fremoving+classes+to%2Ffrom+elements&quot;&gt;Adding/removing classes to/from elements&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Get+current+date+as+YYYY-MM-DD&quot;&gt;Get current date as YYYY-MM-DD&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#escape+HTML+characters&quot;&gt;escape HTML characters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Copy+to+Clipboard&quot;&gt;Copy to Clipboard&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/js-icon.png&quot; alt=&quot;icon&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;Compute+how+an+element+is+hidden&quot; name=&quot;Compute+how+an+element+is+hidden&quot;&gt;Compute how an element is hidden&lt;/h2&gt;
&lt;p&gt;Check if element is hidden&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function isHidden(el) {
    var style = window.getComputedStyle(el);
    return (style.display === &#039;none&#039;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Iterate+over+LI+elements&quot; name=&quot;Iterate+over+LI+elements&quot;&gt;Iterate over LI elements&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;ul id=&quot;foo&quot;&amp;gt;
  &amp;lt;li&amp;gt;First&amp;lt;/li&amp;gt;
  &amp;lt;li&amp;gt;Second&amp;lt;/li&amp;gt;
  &amp;lt;li&amp;gt;Third&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;you can access the list items this way,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;var ul = document.getElementById(&quot;foo&quot;);
var items = ul.getElementsByTagName(&quot;li&quot;);
for (var i = 0; i &amp;lt; items.length; ++i) {
  // do something with items[i], which is a &amp;lt;li&amp;gt; element
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Adding%2Fremoving+classes+to%2Ffrom+elements&quot; name=&quot;Adding%2Fremoving+classes+to%2Ffrom+elements&quot;&gt;Adding/removing classes to/from elements&lt;/h2&gt;
&lt;p&gt;This snippet will add class &lt;code&gt;foo&lt;/code&gt; to all the elements with &lt;code&gt;spa&lt;/code&gt; class:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;var items = document.querySelectorAll(&quot;.spa&quot;);
items.forEach(item =&amp;gt; {
  item.classList.add(&quot;foo&quot;);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This snippet will remove class &lt;code&gt;foo&lt;/code&gt; from all the elements with &lt;code&gt;spa&lt;/code&gt; class:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;var items = document.querySelectorAll(&quot;.spa&quot;);
items.forEach(item =&amp;gt; {
  item.classList.remove(&quot;foo&quot;);
});&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Get+current+date+as+YYYY-MM-DD&quot; name=&quot;Get+current+date+as+YYYY-MM-DD&quot;&gt;Get current date as YYYY-MM-DD&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function getCurrentDate() {
    const today = new Date();
    const year = today.getFullYear();
    const month = String(today.getMonth() + 1).padStart(2, &#039;0&#039;); // Months are zero-based
    const day = String(today.getDate()).padStart(2, &#039;0&#039;);

    return `${year}-${month}-${day}`;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;escape+HTML+characters&quot; name=&quot;escape+HTML+characters&quot;&gt;escape HTML characters&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function escapeHTML(str) {
    var div = document.createElement(&#039;div&#039;);
    div.appendChild(document.createTextNode(str));
    return div.innerHTML;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Of course, if you want to add strings to HTML you can just:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;document.getElementById(&#039;element-id&#039;).textContent = str;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which will automatically escape HTML.&lt;/p&gt;
&lt;h2 id=&quot;Copy+to+Clipboard&quot; name=&quot;Copy+to+Clipboard&quot;&gt;Copy to Clipboard&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function copyToClipboard(textToCopy) {
  var tempElement = document.createElement(&quot;textarea&quot;);
  tempElement.value = textToCopy;
  document.body.appendChild(tempElement);
  tempElement.select();
  document.execCommand(&quot;copy&quot;);
  document.body.removeChild(tempElement);
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Creates a temporary &lt;code&gt;textarea&lt;/code&gt; element, sets its value to the string you want to copy,
and appends it to the document body.&lt;/li&gt;
&lt;li&gt;The function then selects the text inside the temporary &lt;code&gt;textarea&lt;/code&gt;, executes the copy
command, and removes the textarea from the document body.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The reason we temporarily add a &lt;code&gt;textarea&lt;/code&gt; element to the document is to leverage the
browser’s built-in copy functionality, which typically works only on user-selected text
or content within a focusable element like an input or textarea.&lt;/p&gt;
&lt;p&gt;This workaround is necessary because &lt;code&gt;document.execCommand(&#039;copy&#039;)&lt;/code&gt; relies on the text
being selected within an editable field, and a hidden &lt;code&gt;textarea&lt;/code&gt; is a simple way to
fulfill that requirement. Without adding it to the document, the text wouldn&#039;t be
part of the DOM, and the copy command wouldn’t work.&lt;/p&gt;</content>
</entry>
<entry>
<title>Additional OpenSSL tips</title>
<link href="https://www.0ink.net/posts/2026/2026-03-15-other-openssl-tips.html"></link>
<id>urn:uuid:b3db12eb-7ced-e90d-9743-3320f66023a1</id>
<updated>2025-05-12T00:00:00+02:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Self-signed certificates
Display cert extensions
Viewing certificate information
Checking server certificate
How to create a certificate chain ?
...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Self-signed+certificates&quot;&gt;Self-signed certificates&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Display+cert+extensions&quot;&gt;Display cert extensions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Viewing+certificate+information&quot;&gt;Viewing certificate information&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Checking+server+certificate&quot;&gt;Checking server certificate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#How+to+create+a+certificate+chain+%3F&quot;&gt;How to create a certificate chain ?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Converting+a+.pem+certificate+to+pkcs12&quot;&gt;Converting a .pem certificate to pkcs12&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#New+Apple+requirements+since+2023&quot;&gt;New Apple requirements since 2023&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Create+a+self-signed+CA&quot;&gt;Create a self-signed CA&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Create+a+Server+Certificate+Signing+Request&quot;&gt;Create a Server Certificate Signing Request&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Sign+the+Certificate+Request&quot;&gt;Sign the Certificate Request&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#References&quot;&gt;References&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/cas/banner.png&quot; alt=&quot;banner&quot; /&gt;&lt;/p&gt;
&lt;p&gt;So the last few articles we have been exploring the world of
certificate authorities (CA).  This is an addendum to that
covering simple one off tasks that are useful in the
context certificates.&lt;/p&gt;
&lt;h2 id=&quot;Self-signed+certificates&quot; name=&quot;Self-signed+certificates&quot;&gt;Self-signed certificates&lt;/h2&gt;
&lt;p&gt;Obviously, if you have a CA, you shouldn&#039;t need a self-signed
certificate.  However, being able to issue self-signed
certificates can be useful specially in test scenarios.&lt;/p&gt;
&lt;p&gt;This is the command I use for this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;    openssl req -newkey rsa:4096 \
            -x509 \
            -sha256 \
            -days 3650 \
            -nodes \
            -out &quot;$fqdn.crt&quot; \
            -keyout &quot;$fqdn.key&quot; \
            -subj &quot;/CN=$fqdn&quot; \
            -addext &quot;subjectAltName=DNS:*.api.$fqdn,DNS:www.$fqdn,IP:10.0.0.1&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Replace &lt;code&gt;$fqdn&lt;/code&gt; with the full domain name.&lt;/p&gt;
&lt;p&gt;This creates a pair of files &lt;code&gt;$fqdn.crt&lt;/code&gt; and &lt;code&gt;$fqdn.key&lt;/code&gt; containing the certificate
and key pair to add to your web server configuration.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;-subj &quot;/CN=$fqdn&quot;&lt;/code&gt; refers to the main name for the server.  For example:
&lt;code&gt;example.com&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;-addext&lt;/code&gt; is if you want alternative names for the certificate.  Usually this
is useful for implementing wildcard certificates.  It is optional.&lt;/p&gt;
&lt;h2 id=&quot;Display+cert+extensions&quot; name=&quot;Display+cert+extensions&quot;&gt;Display cert extensions&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ openssl x509 -purpose -in root_ca.pem

Certificate purposes:
SSL client : Yes
SSL client CA : Yes
SSL server : Yes
SSL server CA : Yes
Netscape SSL server : Yes
Netscape SSL server CA : Yes
S/MIME signing : Yes
S/MIME signing CA : Yes
S/MIME encryption : Yes
S/MIME encryption CA : Yes
CRL signing : Yes
CRL signing CA : Yes
Any Purpose : Yes
Any Purpose CA : Yes
OCSP helper : Yes
OCSP helper CA : Yes&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Viewing+certificate+information&quot; name=&quot;Viewing+certificate+information&quot;&gt;Viewing certificate information&lt;/h2&gt;
&lt;p&gt;This is done using the x509 command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ openssl x509 -in RapidSSLCA.pem -noout -text

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 145105 (0x236d1)
        Signature Algorithm: sha1WithRSAEncryption
        Issuer: C=US, O=GeoTrust Inc., CN=GeoTrust Global CA
        Validity
            Not Before: Feb 19 22:45:05 2010 GMT
            Not After : Feb 18 22:45:05 2020 GMT
        Subject: C=US, O=GeoTrust, Inc., CN=RapidSSL CA
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
            RSA Public Key: (2048 bit)
                Modulus (2048 bit):
                    00:c7:71:f8:56:c7:1e:d9:cc:b5:ad:f6:b4:97:a3:
                    fb:a1:e6:0b:50:5f:50:aa:3a:da:0f:fc:3d:29:24:
                    43:c6:10:29:c1:fc:55:40:72:ee:bd:ea:df:9f:b6:
                    41:f4:48:4b:c8:6e:fe:4f:57:12:8b:5b:fa:92:dd:
                    5e:e8:ad:f3:f0:1b:b1:7b:4d:fb:cf:fd:d1:e5:f8:
                    e3:dc:e7:f5:73:7f:df:01:49:cf:8c:56:c1:bd:37:
                    e3:5b:be:b5:4f:8b:8b:f0:da:4f:c7:e3:dd:55:47:
                    69:df:f2:5b:7b:07:4f:3d:e5:ac:21:c1:c8:1d:7a:
                    e8:e7:f6:0f:a1:aa:f5:6f:de:a8:65:4f:10:89:9c:
                    03:f3:89:7a:a5:5e:01:72:33:ed:a9:e9:5a:1e:79:
                    f3:87:c8:df:c8:c5:fc:37:c8:9a:9a:d7:b8:76:cc:
                    b0:3e:e7:fd:e6:54:ea:df:5f:52:41:78:59:57:ad:
                    f1:12:d6:7f:bc:d5:9f:70:d3:05:6c:fa:a3:7d:67:
                    58:dd:26:62:1d:31:92:0c:79:79:1c:8e:cf:ca:7b:
                    c1:66:af:a8:74:48:fb:8e:82:c2:9e:2c:99:5c:7b:
                    2d:5d:9b:bc:5b:57:9e:7c:3a:7a:13:ad:f2:a3:18:
                    5b:2b:59:0f:cd:5c:3a:eb:68:33:c6:28:1d:82:d1:
                    50:8b
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Key Usage: critical
                Certificate Sign, CRL Sign
            X509v3 Subject Key Identifier: 
                6B:69:3D:6A:18:42:4A:DD:8F:02:65:39:FD:35:24:86:78:91:16:30
            X509v3 Authority Key Identifier: 
                keyid:C0:7A:98:68:8D:89:FB:AB:05:64:0C:11:7D:AA:7D:65:B8:CA:CC:4E

            X509v3 Basic Constraints: critical
                CA:TRUE, pathlen:0
            X509v3 CRL Distribution Points: 
                URI:http://crl.geotrust.com/crls/gtglobal.crl

            Authority Information Access: 
                OCSP - URI:http://ocsp.geotrust.com

    Signature Algorithm: sha1WithRSAEncryption
        ab:bc:bc:0a:5d:18:94:e3:c1:b1:c3:a8:4c:55:d6:be:b4:98:
        f1:ee:3c:1c:cd:cf:f3:24:24:5c:96:03:27:58:fc:36:ae:a2:
        2f:8f:f1:fe:da:2b:02:c3:33:bd:c8:dd:48:22:2b:60:0f:a5:
        03:10:fd:77:f8:d0:ed:96:67:4f:fd:ea:47:20:70:54:dc:a9:
        0c:55:7e:e1:96:25:8a:d9:b5:da:57:4a:be:8d:8e:49:43:63:
        a5:6c:4e:27:87:25:eb:5b:6d:fe:a2:7f:38:28:e0:36:ab:ad:
        39:a5:a5:62:c4:b7:5c:58:2c:aa:5d:01:60:a6:62:67:a3:c0:
        c7:62:23:f4:e7:6c:46:ee:b5:d3:80:6a:22:13:d2:2d:3f:74:
        4f:ea:af:8c:5f:b4:38:9c:db:ae:ce:af:84:1e:a6:f6:34:51:
        59:79:d3:e3:75:dc:bc:d7:f3:73:df:92:ec:d2:20:59:6f:9c:
        fb:95:f8:92:76:18:0a:7c:0f:2c:a6:ca:de:8a:62:7b:d8:f3:
        ce:5f:68:bd:8f:3e:c1:74:bb:15:72:3a:16:83:a9:0b:e6:4d:
        99:9c:d8:57:ec:a8:01:51:c7:6f:57:34:5e:ab:4a:2c:42:f6:
        4f:1c:89:78:de:26:4e:f5:6f:93:4c:15:6b:27:56:4d:00:54:
        6c:7a:b7:b7&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Checking+server+certificate&quot; name=&quot;Checking+server+certificate&quot;&gt;Checking server certificate&lt;/h2&gt;
&lt;p&gt;This is done using the &lt;code&gt;s_client&lt;/code&gt; command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ openssl s_client -connect www.google.com:443

CONNECTED(00000003)
depth=1 /C=ZA/O=Thawte Consulting (Pty) Ltd./CN=Thawte SGC CA
verify error:num=20:unable to get local issuer certificate
verify return:0
---
Certificate chain
 0 s:/C=US/ST=California/L=Mountain View/O=Google Inc/CN=www.google.com
   i:/C=ZA/O=Thawte Consulting (Pty) Ltd./CN=Thawte SGC CA
 1 s:/C=ZA/O=Thawte Consulting (Pty) Ltd./CN=Thawte SGC CA
   i:/C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIDITCCAoqgAwIBAgIQL9+89q6RUm0PmqPfQDQ+mjANBgkqhkiG9w0BAQUFADBM
MQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkg
THRkLjEWMBQGA1UEAxMNVGhhd3RlIFNHQyBDQTAeFw0wOTEyMTgwMDAwMDBaFw0x
MTEyMTgyMzU5NTlaMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlh
MRYwFAYDVQQHFA1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKFApHb29nbGUgSW5jMRcw
FQYDVQQDFA53d3cuZ29vZ2xlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC
gYEA6PmGD5D6htffvXImttdEAoN4c9kCKO+IRTn7EOh8rqk41XXGOOsKFQebg+jN
gtXj9xVoRaELGYW84u+E593y17iYwqG7tcFR39SDAqc9BkJb4SLD3muFXxzW2k6L
05vuuWciKh0R73mkszeK9P4Y/bz5RiNQl/Os/CRGK1w7t0UCAwEAAaOB5zCB5DAM
BgNVHRMBAf8EAjAAMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwudGhhd3Rl
LmNvbS9UaGF3dGVTR0NDQS5jcmwwKAYDVR0lBCEwHwYIKwYBBQUHAwEGCCsGAQUF
BwMCBglghkgBhvhCBAEwcgYIKwYBBQUHAQEEZjBkMCIGCCsGAQUFBzABhhZodHRw
Oi8vb2NzcC50aGF3dGUuY29tMD4GCCsGAQUFBzAChjJodHRwOi8vd3d3LnRoYXd0
ZS5jb20vcmVwb3NpdG9yeS9UaGF3dGVfU0dDX0NBLmNydDANBgkqhkiG9w0BAQUF
AAOBgQCfQ89bxFApsb/isJr/aiEdLRLDLE5a+RLizrmCUi3nHX4adpaQedEkUjh5
u2ONgJd8IyAPkU0Wueru9G2Jysa9zCRo1kNbzipYvzwY4OA8Ys+WAi0oR1A04Se6
z5nRUP8pJcA2NhUzUnC+MY+f6H/nEQyNv4SgQhqAibAxWEEHXw==
-----END CERTIFICATE-----
subject=/C=US/ST=California/L=Mountain View/O=Google Inc/CN=www.google.com
issuer=/C=ZA/O=Thawte Consulting (Pty) Ltd./CN=Thawte SGC CA
---
No client certificate CA names sent
---
SSL handshake has read 1772 bytes and written 307 bytes
---
New, TLSv1/SSLv3, Cipher is RC4-SHA
Server public key is 1024 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
SSL-Session:
    Protocol  : TLSv1
    Cipher    : RC4-SHA
    Session-ID: BDE08AD29E65CA4007711610FDD69165F9EB47065716D9AB469DAD9882E6E207
    Session-ID-ctx: 
    Master-Key: 6FE2F273ABAAACF8E99180AAB9D540708F6A392DE1285787121B8A438E68FB3D01C127B31CC39146741D3A8396E0FA79
    Key-Arg   : None
    Start Time: 1311928772
    Timeout   : 300 (sec)
    Verify return code: 20 (unable to get local issuer certificate)
---&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Things to note:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The server’s certificate is enclosed by &lt;code&gt;-----BEGIN CERTIFICATE-----&lt;/code&gt; and
&lt;code&gt;-----END CERTIFICATE-----&lt;/code&gt;, this can be saved in a separate file for later reuse.&lt;/li&gt;
&lt;li&gt;The Certificate name, the Subject is
&lt;code&gt;/C=US/ST=California/L=Mountain View/O=Google Inc/CN=www.google.com&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;The Issuer of the certificate, ie the one who signed the certificate, is
&lt;code&gt;/C=ZA/O=Thawte Consulting (Pty) Ltd./CN=Thawte SGC CA&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;We were unable to check the validity of this certificate (unable to get local issuer
certificate) because we do not have the Issuer certifcate.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By giving the &lt;code&gt;-CAfile&lt;/code&gt; parameter we can give the CA certificate file of the CA who
issued the server certificate. Here I guessed the CA certificate file from the Issuer
name:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ openssl s_client -connect www.google.com:443 -CAfile /etc/ssl/certs/Verisign_Class_3_Public_Primary_Certification_Authority.pem

CONNECTED(00000003)
depth=2 /C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority
verify return:1
depth=1 /C=ZA/O=Thawte Consulting (Pty) Ltd./CN=Thawte SGC CA
verify return:1
depth=0 /C=US/ST=California/L=Mountain View/O=Google Inc/CN=www.google.com
verify return:1

---
Certificate chain
 0 s:/C=US/ST=California/L=Mountain View/O=Google Inc/CN=www.google.com
   i:/C=ZA/O=Thawte Consulting (Pty) Ltd./CN=Thawte SGC CA
 1 s:/C=ZA/O=Thawte Consulting (Pty) Ltd./CN=Thawte SGC CA
   i:/C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIDITCCAoqgAwIBAgIQL9+89q6RUm0PmqPfQDQ+mjANBgkqhkiG9w0BAQUFADBM
[ ... ]
SSL-Session:
    Protocol  : TLSv1
    Cipher    : RC4-SHA
    Session-ID: 222E7CFF6DE8425B1B3635F706CDD65083F2758C87095D70E448E27C8272E377
    Session-ID-ctx: 
    Master-Key: B04EBA908A928D7EF7B9432771A0C95A78BA060FBB75383AA081DDF8F8AD31A2F95CD0EF63AC09827A3220E7856EFE28
    Key-Arg   : None
    Start Time: 1311929265
    Timeout   : 300 (sec)
    Verify return code: 0 (ok)
---&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The server certificate is now considered as valid.&lt;/p&gt;
&lt;p&gt;It is not always possible to guess the CA certificate filename, instead use the
&lt;code&gt;-CApath&lt;/code&gt; to give the directory where resides all the known CA certificates of
your system:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ openssl s_client -connect www.google.com:443 -CApath /etc/ssl/certs/

[ ... ]
   Verify return code: 0 (ok)
---&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;How+to+create+a+certificate+chain+%3F&quot; name=&quot;How+to+create+a+certificate+chain+%3F&quot;&gt;How to create a certificate chain ?&lt;/h2&gt;
&lt;p&gt;A certificate chain is just a file where multiple PEM files are concatened. PEM files
must be ordered from the last certificate (for example server certificate or client
certificate) to the top Root CA..&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ cat server_crt.pem operational_ca.pem intermetiade_ca.pem root_ca.pem &amp;gt; certificate_chain.pem&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Converting+a+.pem+certificate+to+pkcs12&quot; name=&quot;Converting+a+.pem+certificate+to+pkcs12&quot;&gt;Converting a .pem certificate to pkcs12&lt;/h2&gt;
&lt;p&gt;PKCS12 is a format to store a certficate AND it’s private key. Because it contains
the private key, it needs to by encrypted. Unlike .pem file, the pkcs12 format is
binary. So you cannot include it in text configuration files; it need to be base64
if needed.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ openssl pkcs12 -export \
          -in &quot;blog.quicheaters.org.pem&quot;
          -inkey &quot;blog.quicheaters.org.key&quot; \
          -out &quot;blog.quicheaters.org.p12&quot; \
          -password &#039;pass:XXXxxxXXX&#039; \
          -certfile &quot;CA/root-ca.pem&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;New+Apple+requirements+since+2023&quot; name=&quot;New+Apple+requirements+since+2023&quot;&gt;New Apple requirements since 2023&lt;/h2&gt;
&lt;p&gt;For its newer iOS and macOS Operating Systems, Apple is enforcing new requirements
for certificates. See &lt;a href=&quot;https://support.apple.com/en-us/HT210176&quot;&gt;apple suppor&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here are ways to create Apple compatible certificates.&lt;/p&gt;
&lt;p&gt;I’m using this small openssl configuration file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ cat openssl.cnf
[ req ]
distinguished_name = req&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Create+a+self-signed+CA&quot; name=&quot;Create+a+self-signed+CA&quot;&gt;Create a self-signed CA&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;openssl req -config ./openssl.cnf \
            -new -x509 -days 3650 -newkey rsa:4096 \
            -keyout CA/-ca.key \
            -passout pass:my_secret_pass \
            -out CA/ca.pem \
            -subj &quot;/C=FR/ST=Ile de France/L=Parise/O=SuperCompany/CN=SuperCompany CA&quot; \
            -addext &#039;basicConstraints=critical,CA:true&#039; \
            -addext &#039;subjectKeyIdentifier=hash&#039; \
            -addext &#039;authorityKeyIdentifier=keyid:always,issuer:always&#039; \
            -addext &#039;keyUsage=critical,cRLSign,digitalSignature,keyCertSign&#039;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Create+a+Server+Certificate+Signing+Request&quot; name=&quot;Create+a+Server+Certificate+Signing+Request&quot;&gt;Create a Server Certificate Signing Request&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;openssl req -config ./openssl.cnf \
            -new -newkey rsa:4096 -nodes \
            -keyout private/vpn.key \
            -out certreqs/vpn.csr \
            -subj &quot;/C=FR/ST=Ile de France/L=Paris/O=SuperCompany/CN=vpn.example.com&quot; \
            -addext &#039;basicConstraints=critical,CA:FALSE&#039; \
            -addext &#039;subjectKeyIdentifier=hash&#039; \
            -addext &#039;keyUsage=critical,nonRepudiation,digitalSignature,keyEncipherment,keyAgreement&#039; \
            -addext &#039;extendedKeyUsage=critical,serverAuth&#039; \
            -addext &#039;subjectAltName=DNS:vpn.example.com,IP:1XX.XX.XX.23&#039;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Sign+the+Certificate+Request&quot; name=&quot;Sign+the+Certificate+Request&quot;&gt;Sign the Certificate Request&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;openssl x509 \
        -req -in certreqs/vpn.csr \
        -CA CA/ca.pem -CAkey CA/ca.key -CAcreateserial \
        -passin pass:my_secret_pass \
        -days 825 \
        -out newcerts/vpn.pem \
        -extfile &amp;lt;(echo &#039;basicConstraints=critical,CA:FALSE&#039;;
                   echo &#039;subjectKeyIdentifier=hash&#039;;
                   echo &#039;authorityKeyIdentifier=keyid:always,issuer:always&#039;;
                   echo &#039;keyUsage=critical,nonRepudiation,digitalSignature,keyEncipherment,keyAgreement&#039;;
                   echo &#039;extendedKeyUsage=critical,serverAuth&#039;;
                   echo &#039;subjectAltName=DNS:vpn.example.com,IP:1XX.XX.XX.23&#039;;)&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;References&quot; name=&quot;References&quot;&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://klyr.github.io/posts/various_reminders_about_openssl_and_certificates/&quot;&gt;https://klyr.github.io/posts/various_reminders_about_openssl_and_certificates/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://support.apple.com/en-us/HT210176&quot;&gt;Apple requirements for trusted certificates&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Deploying your CA&#039;s root certificate</title>
<link href="https://www.0ink.net/posts/2026/2026-03-01-deploy-root-certs.html"></link>
<id>urn:uuid:edb33a93-df03-6154-2693-a5d632d34b6a</id>
<updated>2025-05-12T00:00:00+02:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Linux
Windows 10
iOS 14
Authenticating clients
Authenticating clients with nginx
...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Linux&quot;&gt;Linux&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Windows+10&quot;&gt;Windows 10&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#iOS+14&quot;&gt;iOS 14&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Authenticating+clients&quot;&gt;Authenticating clients&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Authenticating+clients+with+nginx&quot;&gt;Authenticating clients with nginx&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Authenticating+clients+with+apache&quot;&gt;Authenticating clients with apache&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Testing+client+certificates&quot;&gt;Testing client certificates&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Importing+the+Client+certificate+onto+a+Windows+machine&quot;&gt;Importing the Client certificate onto a Windows machine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Firefox&quot;&gt;Firefox&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Chrome+based+%28Chromium%2C+Brave%29+on+Linux&quot;&gt;Chrome based (Chromium, Brave) on Linux&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Chrome+based+%28Chromium%2C+Brave%29+on+Windows&quot;&gt;Chrome based (Chromium, Brave) on Windows&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#References&quot;&gt;References&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/cas/banner.png&quot; alt=&quot;banner&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In my  &lt;a href=&quot;/posts/2026/2026-02-01-manual-ca.html&quot;&gt;previous article&lt;/a&gt;, we implemented
a Certificate Authority (CA) using &lt;code&gt;openssl&lt;/code&gt; commands.&lt;/p&gt;
&lt;p&gt;To become a real CA, you need to get your root certificate on all the devices in the world.
But we don’t need to become a real CA. We just need to be a CA for the devices you own. We need
to add the root certificate to any laptops, desktops, tablets, and phones that access your
HTTPS sites. This can be a bit of a pain, but the good news is that we only have to do it
once. Our root certificate will be good until it expires.&lt;/p&gt;
&lt;p&gt;For client side authentication, you only need to install the root certificates to the
servers that will be doing the client validation.&lt;/p&gt;
&lt;p&gt;Deploying the root certificate will vary for different Operating systems and sometimes
different browsers.&lt;/p&gt;
&lt;h2 id=&quot;Linux&quot; name=&quot;Linux&quot;&gt;Linux&lt;/h2&gt;
&lt;p&gt;There are so many Linux distributions, but Ubuntu is by far the most popular, therefore these
instructions will cover Ubuntu.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;If it isn’t already installed, install the &lt;code&gt;ca-certificates&lt;/code&gt; package.
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo apt-get install -y ca-certificates&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Copy the &lt;code&gt;myCA.pem&lt;/code&gt; or &lt;code&gt;/home/root-ca/certs/ca.cert.pem&lt;/code&gt; file to the
&lt;code&gt;/usr/local/share/ca-certificates&lt;/code&gt; directory as a &lt;code&gt;myCA.crt&lt;/code&gt; file.
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo cp ~/certs/myCA.pem /usr/local/share/ca-certificates/myCA.crt&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Update the certificate store.
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo update-ca-certificates&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can test that the certificate has been installed by running the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;
awk -v cmd=&#039;openssl x509 -noout -subject&#039; &#039;/BEGIN/{close(cmd)};{print | cmd}&#039; &amp;lt; /etc/ssl/certs/ca-certificates.crt | grep Hellfish
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Windows+10&quot; name=&quot;Windows+10&quot;&gt;Windows 10&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Open the “Microsoft Management Console” by using the &lt;code&gt;Windows + R&lt;/code&gt; keyboard combination,
typing &lt;code&gt;mmc&lt;/code&gt; and clicking &lt;strong&gt;Open&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;File &amp;gt; Add/Remove Snap-in&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Certificates&lt;/strong&gt; and &lt;strong&gt;Add&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Computer Account&lt;/strong&gt; and click &lt;strong&gt;Next&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Local Computer&lt;/strong&gt; then click &lt;strong&gt;Finish&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;OK&lt;/strong&gt; to go back to the MMC window&lt;/li&gt;
&lt;li&gt;Double-click &lt;strong&gt;Certificates (local computer)&lt;/strong&gt; to expand the view&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Trusted Root Certification Authorities&lt;/strong&gt;, right-click on &lt;strong&gt;Certificates&lt;/strong&gt; in the
middle column under “Object Type” and select &lt;strong&gt;All Tasks&lt;/strong&gt; then &lt;strong&gt;Import&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Next&lt;/strong&gt; then &lt;strong&gt;Browse&lt;/strong&gt;. Change the certificate extension dropdown next to the
filename field to &lt;strong&gt;All Files (&lt;em&gt;.&lt;/em&gt;)&lt;/strong&gt; and locate the &lt;code&gt;myCA.pem&lt;/code&gt; file, click
&lt;strong&gt;Open&lt;/strong&gt;, then &lt;strong&gt;Next&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Place all certificates in the following store&lt;/strong&gt;. &amp;quot;Trusted Root Certification
Authorities store&amp;quot; is the default. Click &lt;strong&gt;Next&lt;/strong&gt; then click &lt;strong&gt;Finish&lt;/strong&gt; to complete the wizard.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If everything went according to plan, you should see your CA certificate listed under
&lt;strong&gt;Trusted Root Certification Authorities &amp;gt; Certificates&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/cas/ssl-windows-10-trusted-certifcate.png&quot; alt=&quot;banner&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;iOS+14&quot; name=&quot;iOS+14&quot;&gt;iOS 14&lt;/h2&gt;
&lt;p&gt;On iOS devices you can do so fairly easily by following these steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Email the root certificate to yourself, so you can access it on your iOS device. Make sure
to use the default Mail app to access the email.&lt;/li&gt;
&lt;li&gt;Tap on the attachment in the email on your iOS device. It will prompt you to review the profile
in the Settings app.&lt;/li&gt;
&lt;li&gt;Open the Settings app and click &lt;strong&gt;Profile Downloaded&lt;/strong&gt; near the top.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Install&lt;/strong&gt; in the top right, and then &lt;strong&gt;Install&lt;/strong&gt; again on the Warning screen.&lt;/li&gt;
&lt;li&gt;Once installed, hit &lt;strong&gt;Close&lt;/strong&gt; and go back to the main Settings page.&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;General &amp;gt; About.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Scroll to the bottom and click on &lt;strong&gt;Certificate Trust Settings.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Enable your root certificate under “ENABLE FULL TRUST FOR ROOT CERTIFICATES”.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/cas/ssl-ios-trust-certificate-settings.png&quot; alt=&quot;banner&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;Authenticating+clients&quot; name=&quot;Authenticating+clients&quot;&gt;Authenticating clients&lt;/h2&gt;
&lt;p&gt;If you are using certificates to validate clients, you only need to install your
root certificate &lt;code&gt;myCA.pem&lt;/code&gt;.  Depending on your web browser, this might vary.&lt;/p&gt;
&lt;h2 id=&quot;Authenticating+clients+with+nginx&quot; name=&quot;Authenticating+clients+with+nginx&quot;&gt;Authenticating clients with nginx&lt;/h2&gt;
&lt;p&gt;Authenticating clients with nginx, you need to enable TLS first.  &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;server {
    listen              443 ssl;
    server_name         myserver.internal.net;
    ssl_certificate     server.crt;
    ssl_certificate_key server.key;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;
    # ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Configure Nginx to require clients to authenticate with a certificate issued by
your CA To tell Nginx to use mutual TLS and not just one-way TLS, we must instruct
it to require client authentication to ensure clients present a certificate from our
CA when they connect.&lt;/p&gt;
&lt;p&gt;In your server&#039;s configuration block, specify the location of your CA root certificate
to use for authenticating client certificates. You may choose to make client verification
optional so your application can return a 403 message:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;server {
    listen                 443 ssl;
    server_name            myserver.internal.net;
    # ...
    ssl_client_certificate /etc/nginx/client_certs/myca.crt;
    ssl_verify_client      optional;

    # ...

    location / {
      if ($ssl_client_verify != SUCCESS) {
        return 403;
      }
    # ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that in this example &lt;code&gt;ssl_verify_client&lt;/code&gt; is set to &lt;code&gt;optional&lt;/code&gt;.  This allows you to
control what parts of the server need to be authenticated using certificates, and
also lets you return &lt;code&gt;403&lt;/code&gt; error http code.&lt;/p&gt;
&lt;h2 id=&quot;Authenticating+clients+with+apache&quot; name=&quot;Authenticating+clients+with+apache&quot;&gt;Authenticating clients with apache&lt;/h2&gt;
&lt;p&gt;In your Apache configuration file add the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SSLVerifyClient&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SSLVerifyDepth 10&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SSLCACertificateFile /path/to/cert/selfsigned-ca.crt&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Restart apache.&lt;/p&gt;
&lt;h2 id=&quot;Testing+client+certificates&quot; name=&quot;Testing+client+certificates&quot;&gt;Testing client certificates&lt;/h2&gt;
&lt;p&gt;To test this using the command line, you can use &lt;code&gt;curl&lt;/code&gt;, for exampe:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -v --cert client.crt.pem --key client.key.pem https://sample.domain.tld&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also open the URL in your browser and you will be prompted to select a
client certificate for authentication. Depending on your browser and system, you might
have to import the client certificate into the system&#039;s (or browser&#039;s) certificate store
first.&lt;/p&gt;
&lt;p&gt;Create the client keys as described in a &lt;a href=&quot;/posts/2026/2026-02-01-manual-ca.html&quot;&gt;previous article&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For Windows clients, the key material can be combined into a single PFX. You will be
prompted for the passphrase you set above:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;openssl pkcs12 -export -out testuser.pfx -inkey testuser.key -in testuser.crt -certfile myca.crt&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This includes the public portion of your CA’s key to allows Windows to trust your
internally signed CA.&lt;/p&gt;
&lt;h3 id=&quot;Importing+the+Client+certificate+onto+a+Windows+machine&quot; name=&quot;Importing+the+Client+certificate+onto+a+Windows+machine&quot;&gt;Importing the Client certificate onto a Windows machine&lt;/h3&gt;
&lt;p&gt;Double click the .PFX file, select “Current User”.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/cas/windows-import-certificate-lo.png&quot; alt=&quot;windows-import-certificate-lo.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/cas/windows-import-certificate2-lo.png&quot; alt=&quot;windows-import-certificate2-lo.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If you set a passphrase on the PFX above, enter it here. Otherwise, leave blank and hit next.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/cas/windows-import-certificate-passphrase-lo.png&quot; alt=&quot;windows-import-certificate-passphrase-lo.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Next, add the site in question to &amp;quot;trusted sites&amp;quot; in Internet Explorer. This will allow
the client certificate to be sent to the site for veritifcation. (Trusting it in
Internet Explorer will trust it in chrome as well).&lt;/p&gt;
&lt;p&gt;When you next visit the site, you should be prompted to select a client certificate.
Select &amp;quot;OK&amp;quot; and you’re in!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/cas/windows-select-certificate-lo.png&quot; alt=&quot;windows-select-certificate-lo.png&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;Firefox&quot; name=&quot;Firefox&quot;&gt;Firefox&lt;/h3&gt;
&lt;p&gt;Firefox has its own certificate store. In my version, go to &lt;strong&gt;about:preferences#privacy&lt;/strong&gt;,
scroll to the &lt;strong&gt;Certificates&lt;/strong&gt; section, click &lt;strong&gt;&amp;quot;View Certificates...&amp;quot;&lt;/strong&gt;, and on the
first tab (&lt;strong&gt;Your Certificates&lt;/strong&gt;), click &lt;strong&gt;Import&lt;/strong&gt;. Select your &lt;code&gt;.p12&lt;/code&gt; file, enter the
password, and it will be added.&lt;/p&gt;
&lt;h3 id=&quot;Chrome+based+%28Chromium%2C+Brave%29+on+Linux&quot; name=&quot;Chrome+based+%28Chromium%2C+Brave%29+on+Linux&quot;&gt;Chrome based (Chromium, Brave) on Linux&lt;/h3&gt;
&lt;p&gt;On my debian, I have to import client certificate into storage:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pk12util -d sql:$HOME/.pki/nssdb -i certs/johnsmith.p12&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Chrome+based+%28Chromium%2C+Brave%29+on+Windows&quot; name=&quot;Chrome+based+%28Chromium%2C+Brave%29+on+Windows&quot;&gt;Chrome based (Chromium, Brave) on Windows&lt;/h3&gt;
&lt;p&gt;Just double-click on certificate file in explorer, and go everything default. &lt;/p&gt;
&lt;h2 id=&quot;References&quot; name=&quot;References&quot;&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://deliciousbrains.com/ssl-certificate-authority-for-local-https-development/#installing-root-cert&quot;&gt;https://deliciousbrains.com/ssl-certificate-authority-for-local-https-development/#installing-root-cert&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_trusted_certificate&quot;&gt;https://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_trusted_certificate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_verify_client&quot;&gt;https://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_verify_client&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nginx.org/en/docs/http/ngx_http_ssl_module.html#var_ssl_client_cert&quot;&gt;https://nginx.org/en/docs/http/ngx_http_ssl_module.html#var_ssl_client_cert&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://httpd.apache.org/docs/2.4/ssl/ssl_howto.html#allclients&quot;&gt;https://httpd.apache.org/docs/2.4/ssl/ssl_howto.html#allclients&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://knowledge.digicert.com/tutorials/gatekeeper-p12-certificate-installation-method&quot;&gt;https://knowledge.digicert.com/tutorials/gatekeeper-p12-certificate-installation-method&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Revoking Certificates</title>
<link href="https://www.0ink.net/posts/2026/2026-02-15-crls.html"></link>
<id>urn:uuid:b866b3c4-d1cf-f6ab-08e3-5374264b112a</id>
<updated>2025-05-12T00:00:00+02:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Certificate revocation lists
Prepare the configuration file
Create the CRL
Revoke a certificate
Server-side use of the CRL
...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Certificate+revocation+lists&quot;&gt;Certificate revocation lists&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Prepare+the+configuration+file&quot;&gt;Prepare the configuration file&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Create+the+CRL&quot;&gt;Create the CRL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Revoke+a+certificate&quot;&gt;Revoke a certificate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Server-side+use+of+the+CRL&quot;&gt;Server-side use of the CRL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Client-side+use+of+the+CRL&quot;&gt;Client-side use of the CRL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#References&quot;&gt;References&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/cas/banner.png&quot; alt=&quot;banner&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In my  &lt;a href=&quot;/posts/2026/2026-02-01-manual-ca.html&quot;&gt;previous article&lt;/a&gt;, we implemented
a Certificate Authority (CA) using &lt;code&gt;openssl&lt;/code&gt; commands.  This is a continuation
of that article to implement &lt;em&gt;Certificate Revocation Lists&lt;/em&gt; (CRL).&lt;/p&gt;
&lt;p&gt;This article assumes that you implemented the CA as described in my
&lt;a href=&quot;/posts/2026/2026-02-01-manual-ca.html&quot;&gt;previous article&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;Certificate+revocation+lists&quot; name=&quot;Certificate+revocation+lists&quot;&gt;Certificate revocation lists&lt;/h2&gt;
&lt;p&gt;A certificate revocation list (CRL) provides a list of certificates that have been revoked.
A client application, such as a web browser, can use a CRL to check a server’s authenticity.
A server application, such as Apache or OpenVPN, can use a CRL to deny access to clients that
are no longer trusted.&lt;/p&gt;
&lt;p&gt;Publish the CRL at a publicly accessible location (eg,
&lt;code&gt;http://example.com/intermediate.crl.pem&lt;/code&gt;).  Third-parties can fetch the CRL from this
location to check whether any certificates they rely on have been revoked.&lt;/p&gt;
&lt;h2 id=&quot;Prepare+the+configuration+file&quot; name=&quot;Prepare+the+configuration+file&quot;&gt;Prepare the configuration file&lt;/h2&gt;
&lt;p&gt;When a certificate authority signs a certificate, it will normally encode the CRL
location into the certificate. Add &lt;code&gt;crlDistributionPoints&lt;/code&gt; to the appropriate sections.
In our case, add it to the &lt;code&gt;[ server_cert ]&lt;/code&gt; section.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;[ server_cert ]
# ... snipped ...
crlDistributionPoints = URI:http://example.com/intermediate.crl.pem&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Create+the+CRL&quot; name=&quot;Create+the+CRL&quot;&gt;Create the CRL&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# cd /home/int-ca
# openssl ca -config openssl.cnf \
      -gencrl -out crl/intermediate.crl.pem&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Note&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;CRL OPTIONS&lt;/code&gt; section of the &lt;code&gt;ca&lt;/code&gt; man page contains more information on how to create
CRLs.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You can check the contents of the CRL with the crl tool.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# openssl crl -in crl/intermediate.crl.pem -noout -text&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No certificates have been revoked yet, so the output will state &lt;code&gt;No Revoked Certificates&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You should re-create the CRL at regular intervals. By default, the CRL expires after
30 days. This is controlled by the &lt;code&gt;default_crl_days&lt;/code&gt; option in the &lt;code&gt;[ CA_default ]&lt;/code&gt;
section.&lt;/p&gt;
&lt;h2 id=&quot;Revoke+a+certificate&quot; name=&quot;Revoke+a+certificate&quot;&gt;Revoke a certificate&lt;/h2&gt;
&lt;p&gt;Let’s walk through an example. Alice is running the Apache web server and has a private
folder of heart-meltingly cute kitten pictures. Alice wants to grant her friend, Bob,
access to this collection.&lt;/p&gt;
&lt;p&gt;Bob creates a private key and certificate signing request (CSR).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ cd /home/bob
$ openssl genrsa -out bob@example.com.key.pem 2048
$ openssl req -new -key bob@example.com.key.pem \
      -out bob@example.com.csr.pem

You are about to be asked to enter information that will be incorporated
into your certificate request.
-----
Country Name [XX]:US
State or Province Name []:California
Locality Name []:San Francisco
Organization Name []:Bob Ltd
Organizational Unit Name []:
Common Name []:bob@example.com
Email Address []:&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bob sends his CSR to Alice, who then signs it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# cd /home/int-ca
# openssl ca -config openssl.cnf \
      -extensions usr_cert -notext -md sha256 \
      -in csr/bob@example.com.csr.pem \
      -out certs/bob@example.com.cert.pem

Sign the certificate? [y/n]: y
1 out of 1 certificate requests certified, commit? [y/n]: y&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alice verifies that the certificate is valid:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# openssl verify -CAfile intermediate/certs/ca-chain.cert.pem \
      intermediate/certs/bob@example.com.cert.pem

bob@example.com.cert.pem: OK&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;index.txt&lt;/code&gt; file should contain a new entry.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;V 160420124740Z 1001 unknown ... /CN=bob@example.com&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alice sends Bob the signed certificate. Bob installs the certificate in his web
browser and is now able to access Alice’s kitten pictures. Hurray!&lt;/p&gt;
&lt;p&gt;Sadly, it turns out that Bob is misbehaving. Bob has posted Alice’s kitten pictures to
Hacker News, claiming that they’re his own and gaining huge popularity. Alice finds out
and needs to revoke his access immediately.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# cd /hone/int-ca
# openssl ca -config openssl.cnf \
      -revoke certs/bob@example.com.cert.pem

Enter pass phrase for intermediate.key.pem: secretpassword
Revoking Certificate 1001.
Data Base Updated&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The line in &lt;code&gt;index.txt&lt;/code&gt; that corresponds to Bob’s certificate now begins with the character
&lt;code&gt;R&lt;/code&gt;. This means the certificate has been revoked.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;R 160420124740Z 150411125310Z 1001 unknown ... /CN=bob@example.com&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After revoking Bob’s certificate, Alice must re-create the CRL.&lt;/p&gt;
&lt;h2 id=&quot;Server-side+use+of+the+CRL&quot; name=&quot;Server-side+use+of+the+CRL&quot;&gt;Server-side use of the CRL&lt;/h2&gt;
&lt;p&gt;For client certificates, it’s typically a server-side application (eg, Apache) that is doing
the verification. This application needs to have local access to the CRL.&lt;/p&gt;
&lt;p&gt;In Alice’s case, she can add the &lt;code&gt;SSLCARevocationPath&lt;/code&gt; directive to her Apache configuration
and copy the CRL to her web server. The next time that Bob connects to the web server, Apache
will check his client certificate against the CRL and deny access.&lt;/p&gt;
&lt;p&gt;Similarly, OpenVPN has a &lt;code&gt;crl-verify&lt;/code&gt; directive so that it can block clients that have had
their certificates revoked.&lt;/p&gt;
&lt;h2 id=&quot;Client-side+use+of+the+CRL&quot; name=&quot;Client-side+use+of+the+CRL&quot;&gt;Client-side use of the CRL&lt;/h2&gt;
&lt;p&gt;For server certificates, it’s typically a client-side application (eg, a web browser) that
performs the verification. This application must have remote access to the CRL.&lt;/p&gt;
&lt;p&gt;If a certificate was signed with an extension that includes &lt;code&gt;crlDistributionPoints&lt;/code&gt;, a
client-side application can read this information and fetch the CRL from the specified
location.&lt;/p&gt;
&lt;p&gt;The CRL distribution points are visible in the certificate X509v3 details.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# openssl x509 -in cute-kitten-pictures.example.com.cert.pem -noout -text

    X509v3 CRL Distribution Points:

        Full Name:
          URI:http://example.com/intermediate.crl.pem&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;References&quot; name=&quot;References&quot;&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://jamielinux.com/docs/openssl-certificate-authority/introduction.html&quot;&gt;https://jamielinux.com/docs/openssl-certificate-authority/introduction.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Your own Certificate Authority</title>
<link href="https://www.0ink.net/posts/2026/2026-02-01-manual-ca.html"></link>
<id>urn:uuid:f66767c0-097e-0410-daad-a1b171039c7c</id>
<updated>2025-05-12T00:00:00+02:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Generating root cert

Preparation
Creating the root key
Creating the root certificate
...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Generating+root+cert&quot;&gt;Generating root cert&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Preparation&quot;&gt;Preparation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Creating+the+root+key&quot;&gt;Creating the root key&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Creating+the+root+certificate&quot;&gt;Creating the root certificate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Minimalistic+Root+certificate&quot;&gt;Minimalistic Root certificate&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Create+intermediate+cert&quot;&gt;Create intermediate cert&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Prepare+the+directory&quot;&gt;Prepare the directory&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Create+the+intermediate+key&quot;&gt;Create the intermediate key&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Create+the+intermediate+certificate&quot;&gt;Create the intermediate certificate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Verify+the+intermediate+certificate&quot;&gt;Verify the intermediate certificate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Create+the+certificate+chain+file&quot;&gt;Create the certificate chain file&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Minimalistic+Approach&quot;&gt;Minimalistic Approach&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Signing+client%2Fserver+certificates&quot;&gt;Signing client/server certificates&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Create+a+key&quot;&gt;Create a key&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Create+a+certificate&quot;&gt;Create a certificate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Verify+the+certificate&quot;&gt;Verify the certificate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Deploy+the+certificate&quot;&gt;Deploy the certificate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Minimal+approach&quot;&gt;Minimal approach&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#References&quot;&gt;References&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/cas/banner.png&quot; alt=&quot;banner&quot; /&gt;&lt;/p&gt;
&lt;p&gt;As mentioned in the &lt;a href=&quot;/posts/2026/2026-01-15-cas.html&quot;&gt;previous article&lt;/a&gt;,
there are some scenarios that it would be useful to run your own
Certificate Authority (CA).  For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When you require a lot of certificates.&lt;/li&gt;
&lt;li&gt;Authenticate users or client devices.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It is possible to run your own mini Certificate Authority manually.&lt;/p&gt;
&lt;p&gt;To request an SSL certificate from a CA like Verisign or GoDaddy, you send them a
Certificate Signing Request (CSR), and they give you an SSL certificate in return
that they have signed using their root certificate and private key. All browsers
have a copy (or access to a copy from the operating system) of the root certificate
from the various CAs, so the browser can verify that your certificate was signed by
a trusted CA.&lt;/p&gt;
&lt;p&gt;That’s why when you generate a self-signed certificate the browser doesn’t trust it. It
hasn’t been signed by a CA. The way to get around this is to generate our own root
certificate and private key. We then add the root certificate to all the devices we
own just once, and then all the certificates we generate will be inherently trusted.&lt;/p&gt;
&lt;p&gt;This tutorial uses OpenSSL.  OpenSSL is a free and open-source cryptographic library
that provides several command-line tools for handling digital certificates. Some of
these tools can be used to act as a certificate authority.&lt;/p&gt;
&lt;h2 id=&quot;Generating+root+cert&quot; name=&quot;Generating+root+cert&quot;&gt;Generating root cert&lt;/h2&gt;
&lt;p&gt;Acting as a certificate authority (CA) means dealing with cryptographic pairs of
private keys and public certificates. The very first cryptographic pair we’ll create
is the root pair. This consists of the root key (&lt;code&gt;ca.key.pem&lt;/code&gt;) and root certificate
(&lt;code&gt;ca.cert.pem&lt;/code&gt;). This pair forms the identity of your CA.&lt;/p&gt;
&lt;p&gt;Typically, the root CA does not sign server or client certificates directly.
The root CA is only ever used to create one or more intermediate CAs, which are trusted
by the root CA to sign certificates on their behalf. This is best practice. It allows the
root key to be kept offline and unused as much as possible, as any compromise of the root
key is disastrous.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note&lt;/p&gt;
&lt;p&gt;It’s best practice to create the root pair in a secure environment. Ideally, this
should be on a fully encrypted, air gapped computer that is permanently isolated
from the Internet. Remove the wireless card and fill the ethernet port with glue.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;Preparation&quot; name=&quot;Preparation&quot;&gt;Preparation&lt;/h3&gt;
&lt;p&gt;Choose a directory (&lt;code&gt;/home/root-ca&lt;/code&gt;) to store all keys and certificates.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# mkdir /home/root-ca&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create the directory structure. The &lt;code&gt;index.txt&lt;/code&gt; and &lt;code&gt;serial&lt;/code&gt; files act as a flat file
database to keep track of signed certificates.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# cd /home/root-ca
# mkdir certs csr crl newcerts private
# chmod 700 private
# touch index.txt
# echo 1000 &amp;gt; serial&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You must create a configuration file for OpenSSL to use in &lt;code&gt;/home/root-ca/openssl.cnf&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;# OpenSSL root CA configuration file.
# Copy to `/home/root-ca/openssl.cnf`.

[ ca ]
# `man ca`
default_ca = CA_default

[ CA_default ]
# Directory and file locations.
dir               = /home/root-ca
certs             = $dir/certs
crl_dir           = $dir/crl
new_certs_dir     = $dir/newcerts
database          = $dir/index.txt
serial            = $dir/serial
RANDFILE          = $dir/private/.rand

# The root key and root certificate.
private_key       = $dir/private/ca.key.pem
certificate       = $dir/certs/ca.cert.pem

# For certificate revocation lists.
crlnumber         = $dir/crlnumber
crl               = $dir/crl/ca.crl.pem
crl_extensions    = crl_ext
default_crl_days  = 30

# SHA-1 is deprecated, so use SHA-2 instead.
default_md        = sha256

name_opt          = ca_default
cert_opt          = ca_default
default_days      = 375
preserve          = no
policy            = policy_strict

[ policy_strict ]
# The root CA should only sign intermediate certificates that match.
# See the POLICY FORMAT section of `man ca`.
countryName             = match
stateOrProvinceName     = match
organizationName        = match
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ policy_loose ]
# Allow the intermediate CA to sign a more diverse range of certificates.
# See the POLICY FORMAT section of the `ca` man page.
countryName             = optional
stateOrProvinceName     = optional
localityName            = optional
organizationName        = optional
# organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ req ]
# Options for the `req` tool (`man req`).
default_bits        = 2048
distinguished_name  = req_distinguished_name
string_mask         = utf8only

# SHA-1 is deprecated, so use SHA-2 instead.
default_md          = sha256

# Extension to add when the -x509 option is used.
x509_extensions     = v3_ca

[ req_distinguished_name ]
# See &amp;lt;https://en.wikipedia.org/wiki/Certificate_signing_request&amp;gt;.
countryName                     = Country Name (2 letter code)
# stateOrProvinceName             = State or Province Name
localityName                    = Locality Name
# 0.organizationName              = Organization Name
# organizationalUnitName          = Organizational Unit Name
commonName                      = Common Name
emailAddress                    = Email Address

# Optionally, specify some defaults.
countryName_default             = NL
# stateOrProvinceName_default   = 
localityName_default            = Den Haag
# 0.organizationName_default      = Alice Ltd
# organizationalUnitName_default  =
emailAddress_default            =

[ v3_ca ]
# Extensions for a typical CA (`man x509v3_config`).
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ v3_intermediate_ca ]
# Extensions for a typical intermediate CA (`man x509v3_config`).
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ usr_cert ]
# Extensions for client certificates (`man x509v3_config`).
basicConstraints = CA:FALSE
nsCertType = client, email
nsComment = &quot;OpenSSL Generated Client Certificate&quot;
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, emailProtection

[ server_cert ]
# Extensions for server certificates (`man x509v3_config`).
basicConstraints = CA:FALSE
nsCertType = server
nsComment = &quot;OpenSSL Generated Server Certificate&quot;
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;[ ca ]&lt;/code&gt; section is mandatory. Here we tell OpenSSL to use the options from
the &lt;code&gt;[ CA_default ]&lt;/code&gt; section.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;[ CA_default ]&lt;/code&gt; section contains a range of defaults. Make sure you declare the
directory you chose earlier (&lt;code&gt;/home/ca&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;We’ll apply &lt;code&gt;policy_strict&lt;/code&gt; for all root CA signatures, as the root CA is only being
used to create intermediate CAs.&lt;/p&gt;
&lt;p&gt;We’ll apply &lt;code&gt;policy_loose&lt;/code&gt; for all intermediate CA signatures, as the intermediate CA is
signing server and client certificates that may come from a variety of third-parties.&lt;/p&gt;
&lt;p&gt;Options from the &lt;code&gt;[ req ]&lt;/code&gt; section are applied when creating certificates or certificate
signing requests.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;[ req_distinguished_name ]&lt;/code&gt; section declares the information normally required in a
certificate signing request. You can optionally specify some defaults.&lt;/p&gt;
&lt;p&gt;The next few sections are extensions that can be applied when signing certificates.
For example, passing the &lt;code&gt;-extensions v3_ca&lt;/code&gt; command-line argument will apply the options set
in &lt;code&gt;[ v3_ca ]&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We’ll apply the &lt;code&gt;v3_ca&lt;/code&gt; extension when we create the root certificate.&lt;/p&gt;
&lt;p&gt;We’ll apply the &lt;code&gt;v3_ca_intermediate&lt;/code&gt; extension when we create the intermediate
certificate. &lt;code&gt;pathlen:0&lt;/code&gt; ensures that there can be no further certificate authorities
below the intermediate CA.&lt;/p&gt;
&lt;p&gt;We’ll apply the &lt;code&gt;usr_cert&lt;/code&gt; extension when signing client certificates, such as those used
for remote user authentication.&lt;/p&gt;
&lt;p&gt;We’ll apply the &lt;code&gt;server_cert&lt;/code&gt; extension when signing server certificates, such as those used
for web servers.&lt;/p&gt;
&lt;h3 id=&quot;Creating+the+root+key&quot; name=&quot;Creating+the+root+key&quot;&gt;Creating the root key&lt;/h3&gt;
&lt;p&gt;Create the root key (&lt;code&gt;ca.key.pem&lt;/code&gt;) and keep it absolutely secure. Anyone in possession
of the root key can issue trusted certificates. Encrypt the root key with AES 256-bit encryption
and a strong password.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note&lt;/p&gt;
&lt;p&gt;Use 4096 bits for all root and intermediate certificate authority keys. You’ll
still be able to sign server and client certificates of a shorter length.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# cd /home/root-ca
# openssl genrsa -aes256 -passout pass:secretpassword -out private/ca.key.pem 4096
# chmod 400 private/ca.key.pem&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Creating+the+root+certificate&quot; name=&quot;Creating+the+root+certificate&quot;&gt;Creating the root certificate&lt;/h3&gt;
&lt;p&gt;Use the root key (&lt;code&gt;ca.key.pem&lt;/code&gt;) to create a root certificate (&lt;code&gt;ca.cert.pem&lt;/code&gt;). Give the
root certificate a long expiry date, such as twenty years. Once the root certificate
expires, all certificates signed by the CA become invalid.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Warning&lt;/p&gt;
&lt;p&gt;Whenever you use the req tool, you must specify a configuration file to use with the
-config option, otherwise OpenSSL will default to /etc/pki/tls/openssl.cnf.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# cd /root/root-ca
# openssl req -config openssl.cnf \
      -passin pass:secretpassword \
      -key private/ca.key.pem \
      -new -x509 -days 7300 -sha256 -extensions v3_ca \
      -out certs/ca.cert.pem \
      -subj &quot;/C=NL/ST=ZH/O=Alice Ltd/CN=Alice Ltd Root CA&quot;
# chmod 444 certs/ca.cert.pem&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To verify the root certificate do:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# openssl x509 -noout -text -in certs/ca.cert.pem&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output shows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the &lt;code&gt;Signature Algorithm&lt;/code&gt; used&lt;/li&gt;
&lt;li&gt;the dates of certificate &lt;code&gt;Validity&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;Public-Key&lt;/code&gt; bit length&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;Issuer&lt;/code&gt;, which is the entity that signed the certificate&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;Subject&lt;/code&gt;, which refers to the certificate itself&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;code&gt;Issuer&lt;/code&gt; and &lt;code&gt;Subject&lt;/code&gt; are identical as the certificate is self-signed.
Note that all root certificates are self-signed.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;        Issuer: C=NL, ST=ZH, O=Alice Ltd, CN=Alice Ltd Root CA
        Validity
            Not Before: May  8 22:29:26 2025 GMT
            Not After : May  3 22:29:26 2045 GMT
        Subject: C=NL, ST=ZH, O=Alice Ltd, CN=Alice Ltd Root CA
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (4096 bit)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output also shows the &lt;strong&gt;X509v3 extensions&lt;/strong&gt;. We applied the &lt;code&gt;v3_ca&lt;/code&gt; extension, so the options
from &lt;code&gt;[ v3_ca ]&lt;/code&gt; should be reflected in the output.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;        X509v3 extensions:
            X509v3 Subject Key Identifier: 
                64:2B:BA:1A:12:9F:D2:B6:62:45:1D:EC:07:17:DC:DB:CA:E8:E9:A5
            X509v3 Authority Key Identifier: 
                64:2B:BA:1A:12:9F:D2:B6:62:45:1D:EC:07:17:DC:DB:CA:E8:E9:A5
            X509v3 Basic Constraints: critical
                CA:TRUE
            X509v3 Key Usage: critical
                Digital Signature, Certificate Sign, CRL Sign
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Minimalistic+Root+certificate&quot; name=&quot;Minimalistic+Root+certificate&quot;&gt;Minimalistic Root certificate&lt;/h3&gt;
&lt;p&gt;Alternatively you can take the minimalistic approach:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create certificate directory
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir ~/ca
cd ~/ca&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Generate the private key:
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;openssl genrsa -des3 -passout pass:secretpassword -out myCA.key 4096&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Generate root certificate:
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;openssl req -x509 -new -nodes -passin pass:secretpassword -key myCA.key \
     -sha256 -days 1825 -out myCA.pem \
     -subj &quot;/C=NL/ST=ZH/O=Alice Ltd/CN=Alice Ltd Root CA&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Verify certificate:
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;openssl x509 -noout -text -in myCA.pem&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can skip the &lt;em&gt;intermediat certificate&lt;/em&gt; steps.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;Create+intermediate+cert&quot; name=&quot;Create+intermediate+cert&quot;&gt;Create intermediate cert&lt;/h2&gt;
&lt;p&gt;An intermediate certificate authority (CA) is an entity that can sign certificates on
behalf of the root CA. The root CA signs the intermediate certificate, forming a chain
of trust.&lt;/p&gt;
&lt;p&gt;The purpose of using an intermediate CA is primarily for security. The root key can be kept
offline and used as infrequently as possible. If the intermediate key is compromised,
the root CA can revoke the intermediate certificate and create a new intermediate
cryptographic pair.&lt;/p&gt;
&lt;h3 id=&quot;Prepare+the+directory&quot; name=&quot;Prepare+the+directory&quot;&gt;Prepare the directory&lt;/h3&gt;
&lt;p&gt;The root CA files are kept in &lt;code&gt;/home/root-ca&lt;/code&gt;. Choose a different directory (&lt;code&gt;/home/int-ca&lt;/code&gt;)
to store the intermediate CA files.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# mkdir /home/int-ca&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create the same directory structure used for the root CA files. It’s convenient to
also create a &lt;code&gt;csr&lt;/code&gt; directory to hold certificate signing requests.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# cd /home/int-ca
# mkdir certs crl csr newcerts private
# chmod 700 private
# touch index.txt
# echo 1000 &amp;gt; serial&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add a &lt;code&gt;crlnumber&lt;/code&gt; file to the intermediate CA directory tree. &lt;code&gt;crlnumber&lt;/code&gt; is used to
keep track of &lt;em&gt;certificate revocation lists&lt;/em&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# echo 1000 &amp;gt; crlnumber&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a file in &lt;code&gt;/home/int-ca/openssl.cnf&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;# OpenSSL intermediate CA configuration file.
# Copy to `/home/int-ca/openssl.cnf`.

[ ca ]
# `man ca`
default_ca = CA_default

[ CA_default ]
# Directory and file locations.
dir               = /home/int-ca
certs             = $dir/certs
crl_dir           = $dir/crl
new_certs_dir     = $dir/newcerts
database          = $dir/index.txt
serial            = $dir/serial
RANDFILE          = $dir/private/.rand

# The root key and root certificate.
private_key       = $dir/private/intermediate.key.pem
certificate       = $dir/certs/intermediate.cert.pem

# For certificate revocation lists.
crlnumber         = $dir/crlnumber
crl               = $dir/crl/intermediate.crl.pem
crl_extensions    = crl_ext
default_crl_days  = 30

# SHA-1 is deprecated, so use SHA-2 instead.
default_md        = sha256

name_opt          = ca_default
cert_opt          = ca_default
default_days      = 375
preserve          = no
policy            = policy_loose

[ policy_strict ]
# The root CA should only sign intermediate certificates that match.
# See the POLICY FORMAT section of `man ca`.
countryName             = match
stateOrProvinceName     = match
organizationName        = match
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ policy_loose ]
# Allow the intermediate CA to sign a more diverse range of certificates.
# See the POLICY FORMAT section of the `ca` man page.
countryName             = optional
stateOrProvinceName     = optional
localityName            = optional
organizationName        = optional
# organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ req ]
# Options for the `req` tool (`man req`).
default_bits        = 2048
distinguished_name  = req_distinguished_name
string_mask         = utf8only

# SHA-1 is deprecated, so use SHA-2 instead.
default_md          = sha256

# Extension to add when the -x509 option is used.
x509_extensions     = v3_ca

[ req_distinguished_name ]
# See &amp;lt;https://en.wikipedia.org/wiki/Certificate_signing_request&amp;gt;.
countryName                     = Country Name (2 letter code)
# stateOrProvinceName             = State or Province Name
localityName                    = Locality Name
# 0.organizationName              = Organization Name
# organizationalUnitName          = Organizational Unit Name
commonName                      = Common Name
emailAddress                    = Email Address

# Optionally, specify some defaults.
countryName_default             = NL
# stateOrProvinceName_default   = 
localityName_default            = Den Haag
# 0.organizationName_default      = Alice Ltd
# organizationalUnitName_default  =
emailAddress_default            =

[ v3_ca ]
# Extensions for a typical CA (`man x509v3_config`).
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ v3_intermediate_ca ]
# Extensions for a typical intermediate CA (`man x509v3_config`).
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ usr_cert ]
# Extensions for client certificates (`man x509v3_config`).
basicConstraints = CA:FALSE
nsCertType = client, email
nsComment = &quot;OpenSSL Generated Client Certificate&quot;
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, emailProtection

[ server_cert ]
# Extensions for server certificates (`man x509v3_config`).
basicConstraints = CA:FALSE
nsCertType = server
nsComment = &quot;OpenSSL Generated Server Certificate&quot;
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Five options have changed compared to the root CA configuration file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;[ CA_default ]
dir             = /home/int-ca
private_key     = $dir/private/intermediate.key.pem
certificate     = $dir/certs/intermediate.cert.pem
crl             = $dir/crl/intermediate.crl.pem
policy          = policy_loose&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Create+the+intermediate+key&quot; name=&quot;Create+the+intermediate+key&quot;&gt;Create the intermediate key&lt;/h3&gt;
&lt;p&gt;Create the intermediate key (&lt;code&gt;intermediate.key.pem&lt;/code&gt;). Encrypt the intermediate key with
AES 256-bit encryption and a strong password.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This is done in the &lt;strong&gt;intermediate CA&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# cd /home/int-ca
# openssl genrsa -aes256 \
      -passout pass:secretpassword \
      -out private/intermediate.key.pem 4096
# chmod 400 private/intermediate.key.pem&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Create+the+intermediate+certificate&quot; name=&quot;Create+the+intermediate+certificate&quot;&gt;Create the intermediate certificate&lt;/h3&gt;
&lt;p&gt;Use the intermediate key to create a certificate signing request (CSR). The details
should generally match the root CA. The &lt;strong&gt;Common Name&lt;/strong&gt;, however, must be different.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Warning&lt;/p&gt;
&lt;p&gt;Make sure you specify the intermediate CA configuration file
(&lt;code&gt;/home/int-ca/openssl.cnf&lt;/code&gt;).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# cd /home/int-ca
# openssl req -config openssl.cnf -new -sha256 \
      -passin pass:secretpassword \
      -key private/intermediate.key.pem \
      -out csr/intermediate.csr.pem \
      -subj &quot;/C=NL/ST=ZH/O=Alice Ltd/CN=Alice Ltd Intermediate CA&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To create an intermediate certificate, use the root CA with the &lt;code&gt;v3_intermediate_ca&lt;/code&gt;
extension to sign the intermediate CSR. The intermediate certificate should be valid
for a shorter period than the root certificate. Ten years would be reasonable.&lt;/p&gt;
&lt;p&gt;Copy the intermeida certificates &lt;code&gt;csr&lt;/code&gt; to the root CA.  If you follow
hard security practices you may need to manually copy the &lt;code&gt;csr&lt;/code&gt; file to
the air-gapped root CA system.  In our example, we simply copy:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# cp -av /home/int-ca/csr/intermediate.csr.pem /home/root-ca/csr&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Warning&lt;/p&gt;
&lt;p&gt;This time, specify the root CA configuration file (&lt;code&gt;/home/root-ca/openssl.cnf&lt;/code&gt;).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# cd /home/root-ca
# openssl ca -config openssl.cnf -extensions v3_intermediate_ca \
      -days 3650 -notext -md sha256 \
      -passin pass:secretpassword \
      -in csr/intermediate.csr.pem \
      -out certs/intermediate.cert.pem

Sign the certificate? [y/n]: y
1 out of 1 certificate requests certified, commit? [y/n]y

# chmod 444 certs/intermediate.cert.pem&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;index.txt&lt;/code&gt; file is where the OpenSSL &lt;code&gt;ca&lt;/code&gt; tool stores the certificate
database. Do not delete or edit this file by hand. It should now contain a line
that refers to the intermediate certificate.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;V   350506223334Z       1000    unknown /C=NL/ST=ZH/O=Alice Ltd/CN=Alice Ltd Intermediate CA&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Verify+the+intermediate+certificate&quot; name=&quot;Verify+the+intermediate+certificate&quot;&gt;Verify the intermediate certificate&lt;/h3&gt;
&lt;p&gt;As we did for the root certificate, check that the details of the intermediate certificate are correct.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# openssl x509 -noout -text \
      -in certs/intermediate.cert.pem&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Verify the intermediate certificate against the root certificate. An OK indicates that the
chain of trust is intact.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# openssl verify -CAfile certs/ca.cert.pem \
      certs/intermediate.cert.pem

intermediate.cert.pem: OK&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Create+the+certificate+chain+file&quot; name=&quot;Create+the+certificate+chain+file&quot;&gt;Create the certificate chain file&lt;/h3&gt;
&lt;p&gt;When an application (eg, a web browser) tries to verify a certificate signed by
the intermediate CA, it must also verify the intermediate certificate against the root
certificate. To complete the chain of trust, create a CA certificate chain to present
to the application.&lt;/p&gt;
&lt;p&gt;To create the CA certificate chain, concatenate the intermediate and root certificates
together. We will use this file later to verify certificates signed by the intermediate CA.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# cat certs/intermediate.cert.pem \
      certs/ca.cert.pem &amp;gt; certs/intermediate-full-chain.cert.pem
# chmod 444 certs/intermediate-full-chain.cert.pem&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Note&lt;/p&gt;
&lt;p&gt;Our certificate chain file must include the root certificate because no client application
knows about it yet. A better option, particularly if you’re administrating an intranet, is to
install your root certificate on every client that needs to connect. In that case, the chain
file need only contain your intermediate certificate.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;At this point you need to copy from the root CA to the intermediate CA the files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;certs/intermediate.cert.pem&lt;/li&gt;
&lt;li&gt;certs/intermediate-full-chain.cert.pem&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cp -av /home/root-ca/certs/intermediate*.pem /home/int-ca/certs&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Minimalistic+Approach&quot; name=&quot;Minimalistic+Approach&quot;&gt;Minimalistic Approach&lt;/h3&gt;
&lt;p&gt;For the minimalistic approach, this entire section can be skipped.&lt;/p&gt;
&lt;h2 id=&quot;Signing+client%2Fserver+certificates&quot; name=&quot;Signing+client%2Fserver+certificates&quot;&gt;Signing client/server certificates&lt;/h2&gt;
&lt;p&gt;We will be signing certificates using our intermediate CA. You can use these signed
certificates in a variety of situations, such as to secure connections to a web server
or to authenticate clients connecting to a service.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note&lt;/p&gt;
&lt;p&gt;The steps below are from your perspective as the certificate authority. A third-party,
however, can instead create their own private key and certificate signing request (CSR)
without revealing their private key to you. They give you their CSR, and you give back a
signed certificate. In that scenario, skip the genrsa and req commands.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;Create+a+key&quot; name=&quot;Create+a+key&quot;&gt;Create a key&lt;/h3&gt;
&lt;p&gt;Our root and intermediate pairs are 4096 bits. Server and client certificates normally expire
after one year, so we can safely use 2048 bits instead.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note&lt;/p&gt;
&lt;p&gt;Although 4096 bits is slightly more secure than 2048 bits, it slows down TLS handshakes
and significantly increases processor load during handshakes. For this reason, most
websites use 2048-bit pairs.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you’re creating a cryptographic pair for use with a web server (eg, Apache), you probably
want to create a key without a password so that you don&#039;t need to enter the password
every time you restart the web server:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# cd /home/int-ca
# openssl genrsa  \
      -out private/www.example.com.key.pem 2048
# chmod 400 private/www.example.com.key.pem&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On the other hand, if you are creating a key for an interactive user, creating
a key with password would be beneficial:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# cd /home/int-ca
# openssl genrsa  \
      -aes256 \
      -passout pass:secretpassword \
      -out private/user.alice.key.pem 2048
# chmod 400 private/user.alice.key.pem&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Create+a+certificate&quot; name=&quot;Create+a+certificate&quot;&gt;Create a certificate&lt;/h3&gt;
&lt;p&gt;Use the private key to create a certificate signing request (CSR). The CSR details don’t
need to match the intermediate CA. For server certificates, the &lt;strong&gt;Common Name&lt;/strong&gt; must be a
fully qualified domain name (eg, &lt;code&gt;www.example.com&lt;/code&gt;), whereas for client certificates it can
be any unique identifier (eg, an e-mail address). Note that the &lt;strong&gt;Common Name&lt;/strong&gt; cannot be the
same as either your root or intermediate certificate.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# cd /home/int-ca
# openssl req -config openssl.cnf \
      -key private/www.example.com.key.pem \
      -new -sha256 -out csr/www.example.com.csr.pem \
      -subj &quot;/C=NL/CN=www.example.com&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For the user case you may use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# cd /home/int-ca
# openssl req -config openssl.cnf \
      -passin pass:secretpassword \
      -key private/user.alice.key.pem \
      -new -sha256 -out csr/user.alice.csr.pem \
      -subj &quot;/C=NL/CN=user.alice/emailAddress=alice@example.com&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To create a certificate, use the intermediate CA to sign the CSR. If the certificate is
going to be used on a server, use the &lt;code&gt;server_cert&lt;/code&gt; extension. If the certificate is going to be
used for user authentication, use the &lt;code&gt;usr_cert&lt;/code&gt; extension. Certificates are usually given a
validity of one year, though a CA will typically give a few days extra for convenience.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# cd /home/int-ca
# openssl ca -config openssl.cnf \
      -passin pass:secretpassword \
      -extensions server_cert -days 375 -notext -md sha256 \
      -in csr/www.example.com.csr.pem \
      -out certs/www.example.com.cert.pem
# chmod 444 certs/www.example.com.cert.pem&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The intermediate/index.txt file should contain a line referring to this new certificate.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;V   350506223334Z       1000    unknown /C=NL/ST=ZH/O=Alice Ltd/CN=Alice Ltd Intermediate CA&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For the user case you may use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# cd /home/int-ca
# openssl ca -config openssl.cnf \
      -passin pass:secretpassword \
      -extensions usr_cert -days 375 -notext -md sha256 \
      -in csr/user.alice.csr.pem \
      -out certs/user.alice.cert.pem
# chmod 444 certs/user.alice.cert.pem&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Verify+the+certificate&quot; name=&quot;Verify+the+certificate&quot;&gt;Verify the certificate&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# openssl x509 -noout -text \
      -in certs/www.example.com.cert.pem&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;Issuer&lt;/code&gt; is the intermediate CA. The &lt;code&gt;Subject&lt;/code&gt; refers to the certificate itself.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;        Issuer: C=NL, ST=ZH, O=Alice Ltd, CN=Alice Ltd Intermediate CA
        Validity
            Not Before: May  8 23:42:47 2025 GMT
            Not After : May 18 23:42:47 2026 GMT
        Subject: C=NL, CN=www.example.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output will also show the &lt;code&gt;X509v3&lt;/code&gt; extensions. When creating the certificate, you used
either the &lt;code&gt;server_cert&lt;/code&gt; or &lt;code&gt;usr_cert&lt;/code&gt; extension. The options from the corresponding configuration section will be reflected in the output.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;        X509v3 extensions:
            X509v3 Basic Constraints: 
                CA:FALSE
            Netscape Cert Type: 
                SSL Server
            Netscape Comment: 
                OpenSSL Generated Server Certificate
            X509v3 Subject Key Identifier: 
                FA:B9:39:0C:09:67:53:20:D1:FC:2D:B9:00:05:91:98:4E:4D:B4:F0
            X509v3 Authority Key Identifier: 
                keyid:D9:13:85:BC:23:8A:24:BA:A0:D9:A2:21:C9:79:95:34:E3:85:E1:2A
                DirName:/C=NL/ST=ZH/O=Alice Ltd/CN=Alice Ltd Root CA
                serial:10:00
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage: 
                TLS Web Server Authentication&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use the CA certificate chain file we created earlier (&lt;code&gt;ca-chain.cert.pem&lt;/code&gt;) to verify that the
new certificate has a valid chain of trust.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# openssl verify -CAfile certs/intermediate-full-chain.cert.pem \
      certs/www.example.com.cert.pem

www.example.com.cert.pem: OK&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Deploy+the+certificate&quot; name=&quot;Deploy+the+certificate&quot;&gt;Deploy the certificate&lt;/h3&gt;
&lt;p&gt;You can now either deploy your new certificate to a server, or distribute the certificate
to a client. When deploying to a server application (eg, Apache), you need to make the following
files available:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;intermediate-full-chain.cert.pem
www.example.com.key.pem
www.example.com.cert.pem&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you’re signing a CSR from a third-party, you don’t have access to their private key
so you only need to give them back the chain file (&lt;code&gt;ca-chain.cert.pem&lt;/code&gt;) and the
certificate (&lt;code&gt;www.example.com.cert.pem&lt;/code&gt;).&lt;/p&gt;
&lt;h3 id=&quot;Minimal+approach&quot; name=&quot;Minimal+approach&quot;&gt;Minimal approach&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Create private key:
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;openssl genrsa -out www.example.com.key 2048&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Generate CSR:
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;openssl req -new \
         -key www.example.com.key \
         -out www.example.com.csr \
         -addext &quot;subjectAltName = DNS:foo.co.uk, DNS:*.app.example.com&quot; \
         -subj &quot;/C=NL/CN=www.example.com&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Create the certificate: using our CSR, the CA private key and the CA certificate:
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;openssl x509 -req \
       -passin pass:secretpassword \
       -CA myCA.pem -CAkey myCA.key -CAcreateserial \
       -days 825 -sha256 \
       -copy_extensions copyall \
       -in www.example.com.csr \
       -out www.example.com.crt&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We now have three files: &lt;code&gt;www.example.com.key&lt;/code&gt; (the private key), &lt;code&gt;www.example.com.csr&lt;/code&gt; (the
certificate signing request, or csr file), and &lt;code&gt;www.example.com.crt&lt;/code&gt; (the signed certificate).
We can configure local web servers to use HTTPS with the private key and the signed certificate.&lt;/p&gt;
&lt;h2 id=&quot;Conclusion&quot; name=&quot;Conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This articles covers two approaches for running Certificate Authorities only
using &lt;code&gt;openssl&lt;/code&gt; commands.&lt;/p&gt;
&lt;p&gt;The full approach can be implementely quite
securely and explicitly support server and client certificates.&lt;/p&gt;
&lt;p&gt;The minimal approach is quite straight forward for a quick and dirty solution.&lt;/p&gt;
&lt;h2 id=&quot;References&quot; name=&quot;References&quot;&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://deliciousbrains.com/ssl-certificate-authority-for-local-https-development/&quot;&gt;https://deliciousbrains.com/ssl-certificate-authority-for-local-https-development/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jamielinux.com/docs/openssl-certificate-authority/introduction.html&quot;&gt;https://jamielinux.com/docs/openssl-certificate-authority/introduction.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Certificate Authorities</title>
<link href="https://www.0ink.net/posts/2026/2026-01-15-cas.html"></link>
<id>urn:uuid:087cdf7c-069e-16c8-8019-6a7be78ab614</id>
<updated>2025-05-12T00:00:00+02:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
For home users there is not much use for running you own
Certificate Authority (CA), and with availability of
Letsencrypt and the plethora of ACME libraries
setting TLS encryption is quite straight forward.

...]]></summary>
<content type="html">&lt;p&gt;&lt;img src=&quot;/images/2026/cas/banner.png&quot; alt=&quot;banner&quot; /&gt;&lt;/p&gt;
&lt;p&gt;For home users there is not much use for running you own
Certificate Authority (CA), and with availability of
&lt;a href=&quot;https://letsencrypt.org/&quot;&gt;Letsencrypt&lt;/a&gt; and the plethora of ACME libraries
setting TLS encryption is quite straight forward.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/cas/letsencrypt.png&quot; alt=&quot;banner&quot; /&gt;&lt;/p&gt;
&lt;p&gt;There are also some alternatives to &lt;a href=&quot;https://letsencrypt.org/&quot;&gt;Letsencrypt&lt;/a&gt; that
offer free certificates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.buypass.com&quot;&gt;Buypass&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://zerossl.com&quot;&gt;ZeroSSL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.ssl.com/&quot;&gt;SSL.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cloud.google.com/blog/products/identity-security/automate-public-certificate-lifecycle-management-via--acme-client-api&quot;&gt;Google Trust Services&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are commercial offerings that have a free tier.&lt;/p&gt;
&lt;p&gt;I myself use &lt;a href=&quot;https://letsencrypt.org/&quot;&gt;letsencrypt&lt;/a&gt; with &lt;a href=&quot;https://github.com/acmesh-official/acme.sh&quot;&gt;acme.sh&lt;/a&gt; because it is
available for Alpine Linux, which is my preferred Operating System
for my home servers.&lt;/p&gt;
&lt;h2 id=&quot;Private+Certificate+Authority&quot; name=&quot;Private+Certificate+Authority&quot;&gt;Private Certificate Authority&lt;/h2&gt;
&lt;p&gt;There are some scenarios that it would be useful to run your own
Certificate Authority (CA).  For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When you require a lot of certificates.&lt;/li&gt;
&lt;li&gt;Authenticate users or client devices.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are some tools that take care of most of the details
on how to run your own CA.&lt;/p&gt;
&lt;p&gt;Still, this may be useful to know in case you ever need to.  I
tried the folowing myself:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/radiac/caman&quot;&gt;caman&lt;/a&gt; &lt;br /&gt;Written in shell script, and support intermediate certificates.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jsha/minica&quot;&gt;minica&lt;/a&gt; &lt;br /&gt;Written in go language.  Very simple and straightforward.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/FiloSottile/mkcert&quot;&gt;mkcert&lt;/a&gt; &lt;br /&gt;Another one in go language.  A simple zero-config tool to make
locally trusted development certificates with any names you&#039;d like.&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>NEW YEAR 2026</title>
<link href="https://www.0ink.net/posts/2026/2026-01-01-newyear.html"></link>
<id>urn:uuid:d1276807-a18a-8a2b-8b84-1f1c4854413e</id>
<updated>2025-05-10T00:00:00+02:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[Happy New Year!

...]]></summary>
<content type="html">&lt;p&gt;Happy New Year!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/newyear2026.png&quot; alt=&quot;HAPPY NEW YEAR&quot; /&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>More Python GUI programming</title>
<link href="https://www.0ink.net/posts/2025/2025-12-15-more-pygui.html"></link>
<id>urn:uuid:ef0f3dbe-f24a-9a7b-40fa-35fc19726458</id>
<updated>2025-05-12T00:00:00+02:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Using threads with Python and Tkinter

Impelementation using queue.Queue
Key Components
Why Use Threads and Queues?
...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Using+threads+with+Python+and+Tkinter&quot;&gt;Using threads with Python and Tkinter&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Impelementation+using+queue.Queue&quot;&gt;Impelementation using queue.Queue&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Key+Components&quot;&gt;Key Components&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Why+Use+Threads+and+Queues%3F&quot;&gt;Why Use Threads and Queues?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Alternative+implementation+using+Pipe%27s&quot;&gt;Alternative implementation using Pipe&#039;s&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Comparison+of+Queue+vs.+Pipe+%2B+Select&quot;&gt;Comparison of Queue vs. Pipe + Select&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Which+One+is+Better%3F&quot;&gt;Which One is Better?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Using+File+handlers&quot;&gt;Using File handlers&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Event+Polling+Mechanism&quot;&gt;Event Polling Mechanism&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Code+Complexity+%26amp%3B+Maintainability&quot;&gt;Code Complexity &amp;amp; Maintainability&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Which+One+is+Better%3F&quot;&gt;Which One is Better?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Alternative+approaches&quot;&gt;Alternative approaches&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;In a previous article, I wrote about &lt;a href=&quot;/posts/2024/2024-03-01-python-gui.html&quot;&gt;Python GUI&lt;/a&gt; programming.&lt;/p&gt;
&lt;p&gt;I used to write simple User Interfaces with &lt;a href=&quot;https://www.tcl.tk/&quot;&gt;Tcl/Tk&lt;/a&gt;.  Nowadays
I find writing &lt;a href=&quot;https://www.python.org/&quot;&gt;Python&lt;/a&gt; more often, so obviously I would
start writing user interfaces in &lt;a href=&quot;https://www.python.org/&quot;&gt;Python&lt;/a&gt; with &lt;a href=&quot;https://en.wikipedia.org/wiki/Tkinter&quot;&gt;tkinter&lt;/a&gt;,
because of my &lt;a href=&quot;https://www.tcl.tk/&quot;&gt;Tcl/Tk&lt;/a&gt; background.&lt;/p&gt;
&lt;p&gt;While &lt;a href=&quot;https://www.tcl.tk/&quot;&gt;Tcl/Tk&lt;/a&gt; has a complete event driven architecture, suitable for scripting,
&lt;a href=&quot;https://www.python.org/&quot;&gt;Python&lt;/a&gt; has a richer set of programming paradims to choose from.&lt;/p&gt;
&lt;p&gt;One common pattern is GUI programming is to use threading to split the UI and
the computations into separate threads.  This has the advantage of making
the computation thread only about doing calculations, while keeping the
UI very responsive.&lt;/p&gt;
&lt;p&gt;In &lt;a href=&quot;https://www.tcl.tk/&quot;&gt;Tcl/Tk&lt;/a&gt; because it did not support threading natively, you would
either have to split the computation tasks into small units or
use multiple processes.&lt;/p&gt;
&lt;p&gt;Both approaches came with different set of disadvantages.&lt;/p&gt;
&lt;p&gt;Splitting computation tasks into small units meant that you in essence
had to program in a cooperative multitasking environment, and split
a large task into smaller tasks so that the UI wouldn&#039;t block for
long periods of time making it unresponsive.  Most of the programming
effort would be then spent creating the smaller units, and making
sure that the application wouldn&#039;t block.&lt;/p&gt;
&lt;p&gt;Alternatively, you could split your program into multiple processes using
network sockets and fileevent handlers to keep the UI responsive.  In here
a lot of programming effort is spent coordinating and sharing the data between
processes.&lt;/p&gt;
&lt;h2 id=&quot;Using+threads+with+Python+and+Tkinter&quot; name=&quot;Using+threads+with+Python+and+Tkinter&quot;&gt;Using threads with Python and Tkinter&lt;/h2&gt;
&lt;p&gt;With &lt;a href=&quot;https://www.python.org/&quot;&gt;python&lt;/a&gt;, the UI thread and separate computation thread
would have been easy to implement becase &lt;a href=&quot;https://www.python.org/&quot;&gt;python&lt;/a&gt; supports
threads natively.  Unfortunatley, because &lt;a href=&quot;https://en.wikipedia.org/wiki/Tkinter&quot;&gt;tkinter&lt;/a&gt;
is based on &lt;a href=&quot;https://www.tcl.tk/&quot;&gt;Tcl/Tk&lt;/a&gt;, it does not really support multiple threads
and has potential concurrency issues.&lt;/p&gt;
&lt;h3 id=&quot;Impelementation+using+queue.Queue&quot; name=&quot;Impelementation+using+queue.Queue&quot;&gt;Impelementation using queue.Queue&lt;/h3&gt;
&lt;p&gt;There is essentially one main approach to implement this in a
thread safe way.  To use a &lt;a href=&quot;https://www.python.org/&quot;&gt;python&lt;/a&gt; synchronization
primitive such as &lt;a href=&quot;https://docs.python.org/3/library/queue.html&quot;&gt;queue.Queue&lt;/a&gt;, and poll it from an &lt;code&gt;after&lt;/code&gt;
function on a regular basis.&lt;/p&gt;
&lt;p&gt;This is an exampe using [queue.Queue]:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import tkinter as tk
import threading
import time
import queue

class App:
    def __init__(self, root):
        self.root = root
        self.root.title(&quot;Counting UI&quot;)

        # Create UI elements
        self.listbox = tk.Listbox(root, width=40, height=20)
        self.listbox.pack()

        self.close_button = tk.Button(root, text=&quot;Close&quot;, command=self.close)
        self.close_button.pack()

        # Create a thread-safe queue
        self.queue = queue.Queue()

        # Start the counting thread
        self.running = True
        self.thread = threading.Thread(target=self.counter_thread, daemon=True)
        self.thread.start()

        # Start polling the queue using after()
        self.poll_queue()

    def counter_thread(self):
        &quot;&quot;&quot;Thread that counts from 1 to 60, adding values to the queue each second.&quot;&quot;&quot;
        for i in range(1, 61):
            if not self.running:
                break
            self.queue.put(str(i))
            time.sleep(1)

        # Signal completion
        self.queue.put(&quot;DONE&quot;)

    def poll_queue(self):
        &quot;&quot;&quot;Polls the queue every 100ms and updates the listbox.&quot;&quot;&quot;
        while not self.queue.empty():
            item = self.queue.get()
            if item == &quot;DONE&quot;:
                self.close()
                return
            self.listbox.insert(tk.END, item)

        if self.running:
            self.root.after(100, self.poll_queue)  # Schedule next poll

    def close(self):
        &quot;&quot;&quot;Closes the application.&quot;&quot;&quot;
        self.running = False
        self.root.quit()

# Run the application
if __name__ == &quot;__main__&quot;:
    root = tk.Tk()
    app = App(root)
    root.mainloop()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This Python script creates a simple GUI application using Tkinter and manages
background processing using a thread and a queue. Here&#039;s what it does:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It opens a Tkinter window with a listbox and a &amp;quot;Close&amp;quot; button.&lt;/li&gt;
&lt;li&gt;A separate thread runs in the background, counting from 1 to 60, adding each number to
a queue once per second.&lt;/li&gt;
&lt;li&gt;The main thread (UI thread) polls the queue every 100ms, retrieving numbers and displaying
them in the listbox.&lt;/li&gt;
&lt;li&gt;When counting reaches 60, the script signals completion with &lt;code&gt;&quot;DONE&quot;&lt;/code&gt;, triggering the
application to close.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Key+Components&quot; name=&quot;Key+Components&quot;&gt;Key Components&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Tkinter UI Setup:&lt;/strong&gt; 
&lt;ul&gt;
&lt;li&gt;A listbox is used to display the counting numbers.&lt;/li&gt;
&lt;li&gt;A &amp;quot;Close&amp;quot; button is provided to manually stop execution.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Threading (&lt;code&gt;counter_thread&lt;/code&gt; function):&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Runs independently from the UI thread to avoid freezing the interface.&lt;/li&gt;
&lt;li&gt;Adds numbers 1 to 60 into the queue every second.&lt;/li&gt;
&lt;li&gt;Uses &lt;code&gt;&quot;DONE&quot;&lt;/code&gt; as a termination signal.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Queue for Communication (&lt;code&gt;queue.Queue()&lt;/code&gt;):&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Ensures thread-safe interaction between the worker thread and the main UI thread.&lt;/li&gt;
&lt;li&gt;Acts as a buffer for numbers produced by the worker thread.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Polling Mechanism (&lt;code&gt;poll_queue&lt;/code&gt; function):&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Checks the queue every 100ms.&lt;/li&gt;
&lt;li&gt;Retrieves numbers and updates the listbox.&lt;/li&gt;
&lt;li&gt;Recognizes &lt;code&gt;&quot;DONE&quot;&lt;/code&gt; as the termination condition and shuts down the UI.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Application Lifecycle (&lt;code&gt;close&lt;/code&gt; function):&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Stops the counting thread gracefully.&lt;/li&gt;
&lt;li&gt;Closes the application window.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;Why+Use+Threads+and+Queues%3F&quot; name=&quot;Why+Use+Threads+and+Queues%3F&quot;&gt;&lt;strong&gt;Why Use Threads and Queues?&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Threading prevents UI freezing&lt;/strong&gt; since time delays (&lt;code&gt;time.sleep&lt;/code&gt;) inside Tkinter&#039;s main
loop would block interactions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Queues ensure thread safety&lt;/strong&gt;, avoiding direct UI modifications from background threads.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Polling allows non-blocking event handling&lt;/strong&gt;, letting Tkinter remain responsive.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Alternative+implementation+using+Pipe%27s&quot; name=&quot;Alternative+implementation+using+Pipe%27s&quot;&gt;Alternative implementation using Pipe&#039;s&lt;/h2&gt;
&lt;p&gt;For comparison, we can achieve a similar result using select and UNIX Pipes.  This in principle
is very similar to what you would do using &lt;a href=&quot;https://www.tcl.tk/&quot;&gt;Tcl/Tk&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;
import tkinter as tk
import threading
import time
import os
import select

class App:
    def __init__(self, root):
        self.root = root
        self.root.title(&quot;Counting UI&quot;)

        # Create UI elements
        self.listbox = tk.Listbox(root, width=40, height=20)
        self.listbox.pack()

        self.close_button = tk.Button(root, text=&quot;Close&quot;, command=self.close)
        self.close_button.pack()

        # Create a pipe
        self.pipe_r, self.pipe_w = os.pipe()

        # Start the thread
        self.running = True
        self.thread = threading.Thread(target=self.counter_thread)
        self.thread.start()

        # Start polling the pipe using after()
        self.poll_pipe()

    def counter_thread(self):
        &quot;&quot;&quot;Thread that counts from 1 to 60, writing to the pipe each second.&quot;&quot;&quot;
        for i in range(1, 61):
            if not self.running:
                break
            os.write(self.pipe_w, f&quot;{i}\n&quot;.encode())
            time.sleep(1)

        # Signal completion by writing &quot;DONE&quot; to the pipe
        os.write(self.pipe_w, &quot;DONE\n&quot;.encode())

    def poll_pipe(self):
        &quot;&quot;&quot;Polls the pipe every 100ms using select() and updates the listbox.&quot;&quot;&quot;
        readable, _, _ = select.select([self.pipe_r], [], [], 0)
        if readable:
            try:
                data = os.read(self.pipe_r, 1024).decode()
                for line in data.splitlines():
                    if line == &quot;DONE&quot;:
                        self.close()  # Call close method in UI thread
                        return
                    self.listbox.insert(tk.END, line)
            except OSError:
                pass  # Pipe closed

        if self.running:
            self.root.after(100, self.poll_pipe)  # Schedule next poll

    def close(self):
        &quot;&quot;&quot;Closes the application.&quot;&quot;&quot;
        self.running = False
        os.close(self.pipe_r)
        os.close(self.pipe_w)
        self.root.quit()

# Run the application
if __name__ == &quot;__main__&quot;:
    root = tk.Tk()
    app = App(root)
    root.mainloop()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both implementations serve the same purpose—communicating between a background
thread and the Tkinter UI thread—but they differ in efficiency, complexity, and reliability.
Here&#039;s a comparison:&lt;/p&gt;
&lt;h3 id=&quot;Comparison+of+Queue+vs.+Pipe+%2B+Select&quot; name=&quot;Comparison+of+Queue+vs.+Pipe+%2B+Select&quot;&gt;Comparison of Queue vs. Pipe + Select&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Queue (&lt;code&gt;queue.Queue&lt;/code&gt;)&lt;/th&gt;
&lt;th&gt;Pipe + Select (&lt;code&gt;os.pipe() + select.select()&lt;/code&gt;)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ease of Use&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Simple API (&lt;code&gt;put/get&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Requires handling raw byte streams&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Polling Mechanism&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Uses &lt;code&gt;queue.get()&lt;/code&gt; (blocking or timeout-based)&lt;/td&gt;
&lt;td&gt;Requires &lt;code&gt;select.select()&lt;/code&gt; polling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Code Readability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Cleaner and more structured&lt;/td&gt;
&lt;td&gt;More low-level, harder to maintain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data Format&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Stores Python objects (strings, numbers)&lt;/td&gt;
&lt;td&gt;Requires encoding/decoding byte streams&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;Which+One+is+Better%3F&quot; name=&quot;Which+One+is+Better%3F&quot;&gt;&lt;strong&gt;Which One is Better?&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Using &lt;code&gt;queue.Queue&lt;/code&gt; is generally better&lt;/strong&gt; in the context of Python multithreading and GUI applications:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;More Pythonic and intuitive:&lt;/strong&gt; Python&#039;s queue mechanisms integrate naturally with threading.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automatic thread safety:&lt;/strong&gt; No need for manual locking or polling overhead.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Better resource management:&lt;/strong&gt; No need to manually open and close OS-level pipes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross-platform reliability:&lt;/strong&gt; Works consistently across Windows, macOS, and Linux.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;However, &lt;strong&gt;Pipe + Select is useful in certain cases&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When dealing with &lt;strong&gt;multi-processing (instead of multi-threading)&lt;/strong&gt;, since &lt;code&gt;queue.Queue&lt;/code&gt; is thread-safe but &lt;strong&gt;not process-safe&lt;/strong&gt; (for processes, &lt;code&gt;multiprocessing.Queue&lt;/code&gt; is preferred).&lt;/li&gt;
&lt;li&gt;If interacting with &lt;strong&gt;external system processes&lt;/strong&gt;, where pipes and low-level I/O are necessary.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For a Tkinter-based GUI with multi-threading, &lt;strong&gt;&lt;code&gt;queue.Queue&lt;/code&gt; is the better choice&lt;/strong&gt; -
it&#039;s safer, simpler, and requires less manual handling compared to pipe-based synchronization.&lt;/p&gt;
&lt;h2 id=&quot;Using+File+handlers&quot; name=&quot;Using+File+handlers&quot;&gt;Using File handlers&lt;/h2&gt;
&lt;p&gt;The alternative implementation using UNIX pipe&#039;s is interesting because it can be
modified to use &lt;a href=&quot;https://www.tcl-lang.org/man/tcl8.6/TclCmd/fileevent.htm&quot;&gt;filehandlers&lt;/a&gt;.  This is closer to a &lt;a href=&quot;https://www.tcl.tk/&quot;&gt;Tcl/Tk&lt;/a&gt; implementation
which would probably use &lt;a href=&quot;https://www.tcl-lang.org/man/tcl8.6/TclCmd/fileevent.htm&quot;&gt;fileevent&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;
import tkinter as tk
import threading
import time
import os

class App:
    def __init__(self, root):
        self.root = root
        self.root.title(&quot;Counting UI&quot;)

        # Create UI elements
        self.listbox = tk.Listbox(root, width=40, height=20)
        self.listbox.pack()

        self.close_button = tk.Button(root, text=&quot;Close&quot;, command=self.close)
        self.close_button.pack()

        # Create a pipe
        self.pipe_r, self.pipe_w = os.pipe()

        # Start the thread
        self.running = True
        self.thread = threading.Thread(target=self.counter_thread)
        self.thread.start()

        # Register the pipe with Tkinter&#039;s event loop
        self.root.createfilehandler(self.pipe_r, tk.READABLE, self.read_pipe)

    def counter_thread(self):
        &quot;&quot;&quot;Thread that counts from 1 to 60, writing to the pipe each second.&quot;&quot;&quot;
        for i in range(1, 61):
            if not self.running:
                break
            os.write(self.pipe_w, f&quot;{i}\n&quot;.encode())
            time.sleep(1)

        # Signal completion by writing &quot;DONE&quot; to the pipe
        os.write(self.pipe_w, &quot;DONE\n&quot;.encode())

    def read_pipe(self, file_descriptor, event_mask):
        &quot;&quot;&quot;Reads from the pipe and updates the listbox.&quot;&quot;&quot;
        try:
            data = os.read(file_descriptor, 1024).decode()
            for line in data.splitlines():
                if line == &quot;DONE&quot;:
                    self.close()  # Call close method in UI thread
                    return
                self.listbox.insert(tk.END, line)
        except OSError:
            pass  # Pipe closed

    def close(self):
        &quot;&quot;&quot;Closes the application.&quot;&quot;&quot;
        self.running = False
        os.close(self.pipe_r)
        os.close(self.pipe_w)
        self.root.quit()

# Run the application
if __name__ == &quot;__main__&quot;:
    root = tk.Tk()
    app = App(root)
    root.mainloop()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both approaches use &lt;strong&gt;pipes (&lt;code&gt;os.pipe()&lt;/code&gt;)&lt;/strong&gt; for communication between a background thread
and the Tkinter UI thread. However, they differ in how they integrate with Tkinter&#039;s event
loop. Here’s a breakdown:&lt;/p&gt;
&lt;h3 id=&quot;Event+Polling+Mechanism&quot; name=&quot;Event+Polling+Mechanism&quot;&gt;Event Polling Mechanism&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Pipe + Select (&lt;code&gt;select.select()&lt;/code&gt;)&lt;/th&gt;
&lt;th&gt;Pipe + &lt;code&gt;createfilehandler()&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Event Handling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Polls the pipe every 100ms using &lt;code&gt;select.select()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Directly registers a pipe handler with Tkinter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Polling Overhead&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Requires frequent polling even when there’s no data&lt;/td&gt;
&lt;td&gt;More efficient—Tkinter triggers event on readable data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Performance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Slightly wasteful due to constant polling&lt;/td&gt;
&lt;td&gt;More optimal as event processing is handled natively by Tkinter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Responsiveness&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;UI remains responsive but polling introduces some delay&lt;/td&gt;
&lt;td&gt;Immediate handling of incoming data with better responsiveness&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;Code+Complexity+%26amp%3B+Maintainability&quot; name=&quot;Code+Complexity+%26amp%3B+Maintainability&quot;&gt;Code Complexity &amp;amp; Maintainability&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Pipe + Select&lt;/th&gt;
&lt;th&gt;Pipe + &lt;code&gt;createfilehandler()&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Complexity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Requires manual polling with &lt;code&gt;select.select()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Simpler, as Tkinter automatically processes pipe events&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Readability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;More boilerplate code for polling&lt;/td&gt;
&lt;td&gt;More concise and integrates cleanly with Tkinter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ease of Debugging&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Requires handling timeout and manual checks&lt;/td&gt;
&lt;td&gt;Easier to debug since Tkinter invokes handler only when needed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;Which+One+is+Better%3F&quot; name=&quot;Which+One+is+Better%3F&quot;&gt;Which One is Better?&lt;/h3&gt;
&lt;p&gt;Both approaches are Linux/UNIX dependant, so they wouldn&#039;t work on MS Windows.  If that is
not a requirement, is obvious that using File Handlers is the better approach as avoids polling
overhead, giving better performance.&lt;/p&gt;
&lt;h2 id=&quot;Alternative+approaches&quot; name=&quot;Alternative+approaches&quot;&gt;Alternative approaches&lt;/h2&gt;
&lt;p&gt;I found &lt;a href=&quot;https://github.com/insolor/async-tkinter-loop&quot;&gt;Asynchronous Tkinter Mainloop&lt;/a&gt; which is an asynchronous implementation
of the mainloop for tkinter. This allows using async handler functions. It is intended to be
as simple to use as possible. No fancy unusual syntax or constructions - just use an
alternative function instead of root.mainloop() and wrap asynchronous handlers into a
helper function.&lt;/p&gt;
&lt;p&gt;This is interesting if you are used to using &lt;a href=&quot;https://www.python.org/&quot;&gt;Python&lt;/a&gt;&#039;s &lt;a href=&quot;https://docs.python.org/3/library/asyncio.html&quot;&gt;asyncio&lt;/a&gt;.  However
keep in mind that this is very similar to using &amp;quot;after+poll&amp;quot; approach mentioned earlier.&lt;/p&gt;
&lt;h2 id=&quot;Conclusion&quot; name=&quot;Conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In conclusion, while &lt;a href=&quot;https://en.wikipedia.org/wiki/Tkinter&quot;&gt;Tkinter&#039;s&lt;/a&gt; threading limitations stem from its &lt;a href=&quot;https://www.tcl.tk/&quot;&gt;Tcl/Tk&lt;/a&gt;
foundation, various strategies exist to maintain a responsive UI alongside background
computations.
Using &lt;a href=&quot;https://docs.python.org/3/library/queue.html&quot;&gt;queue.Queue&lt;/a&gt; provides the most Pythonic and thread-safe approach,
ensuring clear communication between worker threads and the UI.
Pipes and file handlers, while viable, introduce added complexity and platform dependencies.
Meanwhile, asynchronous frameworks like &lt;a href=&quot;https://docs.python.org/3/library/asyncio.html&quot;&gt;asyncio&lt;/a&gt; and event-driven alternatives further
expand possibilities these suffer with the same polling overhead as other solutions.&lt;/p&gt;
&lt;p&gt;Ultimately, the choice depends on your application&#039;s needs—whether you prioritize simplicity,
efficiency, or compatibility. If responsiveness and ease of implementation are your goals,
&lt;a href=&quot;https://docs.python.org/3/library/queue.html&quot;&gt;queue.Queue&lt;/a&gt; stands as the most robust option.
However, developers familiar with lower-level event-driven architectures may find pipes
and file handlers more suitable.
Exploring these approaches helps refine a developer’s understanding of concurrency in GUI
applications while balancing usability, performance, and maintainability.&lt;/p&gt;</content>
</entry>
<entry>
<title>Installing source using PIP</title>
<link href="https://www.0ink.net/posts/2025/2025-12-01-pip-install.html"></link>
<id>urn:uuid:15a3ee8c-11ae-dc12-23de-f879211af841</id>
<updated>2025-05-05T00:00:00+02:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Installing from github
Installing from ZIP file

How It Works:
Alternative: Local ZIP File Installation
...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Installing+from+github&quot;&gt;Installing from github&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Installing+from+ZIP+file&quot;&gt;Installing from ZIP file&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#How+It+Works%3A&quot;&gt;How It Works:&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Alternative%3A+Local+ZIP+File+Installation&quot;&gt;Alternative: Local ZIP File Installation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Creating+a+Python+package&quot;&gt;Creating a Python package&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Explanation%3A&quot;&gt;Explanation:&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Key+Components%3A&quot;&gt;Key Components:&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Single+file+python+modules&quot;&gt;Single file python modules&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#setup.py+Example+for+a+Single+File%3A&quot;&gt;setup.py Example for a Single File:&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Creating+the+ZIP+File%3A&quot;&gt;Creating the ZIP File:&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Installing+with+pip%3A&quot;&gt;Installing with pip:&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/pipinst/banner.png&quot; alt=&quot;banner&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;Installing+from+github&quot; name=&quot;Installing+from+github&quot;&gt;Installing from github&lt;/h2&gt;
&lt;p&gt;You can install a Python module directly from a GitHub repository using &lt;code&gt;pip&lt;/code&gt; by specifying
the repository&#039;s URL. Here’s how you do it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;pip install git+https://github.com/username/repository.git&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/pipinst/github_logo.png&quot; alt=&quot;github logo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Replace &lt;code&gt;username&lt;/code&gt; with the owner of the repository and &lt;code&gt;repository&lt;/code&gt; with the name of the
repository. If the module is inside a subdirectory, or if the repository has different
branches, you might need to specify the path like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;pip install git+https://github.com/username/repository.git@branch_name&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can install a specific tag from a GitHub repository using pip by appending the tag name
to the repository URL with an &lt;code&gt;@&lt;/code&gt; symbol. Here&#039;s how:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;pip install git+https://github.com/username/repository.git@tag_name&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/pipinst/pip_logo.png&quot; alt=&quot;pip logo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;When using &lt;code&gt;pip&lt;/code&gt; to install a package directly from a GitHub repository, &lt;code&gt;pip&lt;/code&gt; interacts with
GitHub as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Fetching the Repository&lt;/strong&gt; – &lt;code&gt;pip&lt;/code&gt; uses Git to clone the specified repository to your local
machine. This is done using the &lt;code&gt;git+https://github.com/...&lt;/code&gt; URL format.   &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Checking Out the Code&lt;/strong&gt; – If you specify a branch, tag, or commit hash, &lt;code&gt;pip&lt;/code&gt; checks out
that specific version of the repository.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Looking for &lt;code&gt;setup.py&lt;/code&gt; or &lt;code&gt;pyproject.toml&lt;/code&gt;&lt;/strong&gt; – Once cloned, &lt;code&gt;pip&lt;/code&gt; searches for a &lt;code&gt;setup.py&lt;/code&gt;
file (for traditional Python packages) or &lt;code&gt;pyproject.toml&lt;/code&gt; (for modern builds using tools like
Poetry). It uses this file to determine dependencies and installation steps.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Installing Dependencies&lt;/strong&gt; – If the package has dependencies listed, &lt;code&gt;pip&lt;/code&gt; installs them
before installing the main package itself.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Building &amp;amp; Installing&lt;/strong&gt; – If the package needs compiling or building wheels, &lt;code&gt;pip&lt;/code&gt; handles
that and then installs the package into your environment.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Essentially, &lt;code&gt;pip&lt;/code&gt; acts as both a package manager and a tool for fetching repositories from GitHub
dynamically. It&#039;s a convenient way to install Python packages that aren&#039;t published on PyPI yet.&lt;/p&gt;
&lt;h2 id=&quot;Installing+from+ZIP+file&quot; name=&quot;Installing+from+ZIP+file&quot;&gt;Installing from ZIP file&lt;/h2&gt;
&lt;p&gt;If you want to install a Python package from a &lt;code&gt;.zip&lt;/code&gt; file hosted on a web server
using &lt;code&gt;pip&lt;/code&gt;, you can do it like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;pip install https://example.com/path/to/package.zip&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/pipinst/winzip_logo.png&quot; alt=&quot;ziplogo&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;How+It+Works%3A&quot; name=&quot;How+It+Works%3A&quot;&gt;How It Works:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pip&lt;/code&gt; downloads the ZIP file from the specified URL.&lt;/li&gt;
&lt;li&gt;It extracts the contents and looks for a &lt;code&gt;setup.py&lt;/code&gt; or &lt;code&gt;pyproject.toml&lt;/code&gt; file to determine
how to install the package.&lt;/li&gt;
&lt;li&gt;If dependencies are listed, &lt;code&gt;pip&lt;/code&gt; installs them before installing the package itself.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Alternative%3A+Local+ZIP+File+Installation&quot; name=&quot;Alternative%3A+Local+ZIP+File+Installation&quot;&gt;Alternative: Local ZIP File Installation&lt;/h3&gt;
&lt;p&gt;If you’ve already downloaded the ZIP file, you can install it locally using:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;pip install /path/to/package.zip&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the package is structured correctly, this method works seamlessly.&lt;/p&gt;
&lt;h2 id=&quot;Creating+a+Python+package&quot; name=&quot;Creating+a+Python+package&quot;&gt;Creating a Python package&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/pipinst/python_logo.png&quot; alt=&quot;python logo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If you&#039;re creating a Python package, the minimal &lt;code&gt;setup.py&lt;/code&gt; file should include:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from setuptools import setup

setup(
    name=&quot;your_package_name&quot;,
    version=&quot;0.1&quot;,
    packages=[&quot;your_package&quot;],
)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alternatively you could:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from setuptools import setup
from source import VERSION

setup(
    name=&quot;your_package_name&quot;,
    version=VERSION,
    packages=[&quot;your_package&quot;],
)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Explanation%3A&quot; name=&quot;Explanation%3A&quot;&gt;Explanation:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;name&lt;/code&gt;: Defines the package name.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;version&lt;/code&gt;: Sets the version number.  In the alternatively option, the version
comes from the source package itself.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;packages&lt;/code&gt;: Specifies the directory containing your Python modules.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For a more complete &lt;code&gt;setup.py&lt;/code&gt;, you&#039;d usually include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;author&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;long_description&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;install_requires&lt;/code&gt; for dependencies&lt;/li&gt;
&lt;li&gt;&lt;code&gt;entry_points&lt;/code&gt; if your package includes a command-line tool&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The ZIP file should be structured like a typical Python package to ensure &lt;code&gt;pip&lt;/code&gt; can
install it properly. Here&#039;s an example of how the contents should be organized:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package.zip
│── your_package/                # Main package directory
│   ├── __init__.py              # Makes it a Python package
│   ├── module.py                # Your actual Python code
│── setup.py                      # Installation configuration
│── requirements.txt              # Optional: Dependencies list
│── README.md                     # Optional: Project info&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I saw examples, where the actual python code is named &lt;code&gt;core.py&lt;/code&gt; and in &lt;code&gt;__init__.py&lt;/code&gt; they
would have:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from .core import *&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You may also want to import or define a &lt;code&gt;VERSION&lt;/code&gt; id.&lt;/p&gt;
&lt;h4 id=&quot;Key+Components%3A&quot; name=&quot;Key+Components%3A&quot;&gt;Key Components:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;your_package/&lt;/code&gt;&lt;/strong&gt; – The actual package directory with your Python modules.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;__init__.py&lt;/code&gt;&lt;/strong&gt; – An empty file (or containing initialization code) that marks the directory
as a package.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;setup.py&lt;/code&gt;&lt;/strong&gt; – Defines metadata, dependencies, and installation instructions for the package.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;requirements.txt&lt;/code&gt;&lt;/strong&gt; – If the package has dependencies, list them here.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;README.md&lt;/code&gt;&lt;/strong&gt; – Optional, but useful for explaining your package.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To create this ZIP file, navigate to the package directory and run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;zip -r package.zip your_package setup.py requirements.txt README.md&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once zipped, &lt;code&gt;pip&lt;/code&gt; can install it like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;pip install https://example.com/package.zip&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can use the same structure for a Git repository.&lt;/p&gt;
&lt;h3 id=&quot;Single+file+python+modules&quot; name=&quot;Single+file+python+modules&quot;&gt;Single file python modules&lt;/h3&gt;
&lt;p&gt;If your module is just a single Python file rather than a full package, you can still create a
ZIP file for installation with &lt;code&gt;pip&lt;/code&gt;. Here’s how your ZIP structure should look:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module.zip
│── module.py              # Your entire Python module
│── setup.py               # Installation configuration
│── requirements.txt       # Optional: Dependencies list
│── README.md              # Optional: Project info&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;setup.py+Example+for+a+Single+File%3A&quot; name=&quot;setup.py+Example+for+a+Single+File%3A&quot;&gt;&lt;code&gt;setup.py&lt;/code&gt; Example for a Single File:&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from setuptools import setup

setup(
    name=&quot;module_name&quot;,
    version=&quot;0.1&quot;,
    py_modules=[&quot;module&quot;],  # Instead of &#039;packages&#039;, use &#039;py_modules&#039;
)&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;Creating+the+ZIP+File%3A&quot; name=&quot;Creating+the+ZIP+File%3A&quot;&gt;Creating the ZIP File:&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;zip -r module.zip module.py setup.py requirements.txt README.md&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;Installing+with+pip%3A&quot; name=&quot;Installing+with+pip%3A&quot;&gt;Installing with &lt;code&gt;pip&lt;/code&gt;:&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;pip install https://example.com/module.zip&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This works similarly to installing a full package, but since there’s only one file, you
use &lt;code&gt;py_modules&lt;/code&gt; instead of &lt;code&gt;packages&lt;/code&gt; in &lt;code&gt;setup.py&lt;/code&gt;.&lt;/p&gt;</content>
</entry>
<entry>
<title>Copper anniversary</title>
<link href="https://www.0ink.net/posts/2025/2025-11-15-12.5-years.html"></link>
<id>urn:uuid:55c9fe5f-721c-aac2-c272-055e7b0644e5</id>
<updated>2024-10-22T00:00:00+02:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[Today, this blog reaches the 12.5 year mark (In the Netherlands, copper annyversary).

Actually 0ink.net has been running for much longer than that,
but as a blog format, it is from 2013-05-15 when it was migrated to Wordpress.
You can find the firs post here.
...]]></summary>
<content type="html">&lt;p&gt;Today, this blog reaches the 12.5 year mark (In the Netherlands, copper annyversary).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/ballon12.5.png&quot; alt=&quot;12.5 years&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Actually &lt;a href=&quot;https://0ink.net&quot;&gt;0ink.net&lt;/a&gt; has been running for much longer than that,
but as a blog format, it is from 2013-05-15 when it was migrated to &lt;a href=&quot;https://wordpress.org/&quot;&gt;Wordpress&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can find the firs post &lt;a href=&quot;/posts/2013/2013-05-09-git-workflows.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;</content>
</entry>
<entry>
<title>Using the Github Container Registry</title>
<link href="https://www.0ink.net/posts/2025/2025-11-01-using-ghcr.html"></link>
<id>urn:uuid:b4db9619-a61f-b4af-d226-c149aae6b193</id>
<updated>2025-05-05T00:00:00+02:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Initialization
Basic env variables
Job declaration
Steps

...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Initialization&quot;&gt;Initialization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Basic+env+variables&quot;&gt;Basic env variables&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Job+declaration&quot;&gt;Job declaration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Steps&quot;&gt;Steps&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Run-time+additional+env+variables&quot;&gt;Run-time additional env variables&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Git+source+checkout&quot;&gt;Git source checkout&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Additional+configuration&quot;&gt;Additional configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Log+in+to+GHCR&quot;&gt;Log in to GHCR&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Container+metadata&quot;&gt;Container metadata&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Container+build+and+push&quot;&gt;Container build and push&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Artifact+attestation&quot;&gt;Artifact attestation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Cleaning+Container+registry&quot;&gt;Cleaning Container registry&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Using+the+image&quot;&gt;Using the image&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/ghcr/ghcr.png&quot; alt=&quot;ghcr&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The GitHub Container Registry (GHCR) allows you to host and manage Docker container images in
your personal or organisation account on GitHub. One of the benefits is that permissions can be
defined for the Docker image, independent of any repository. Thus, your repository could be private
and your Docker image public.&lt;/p&gt;
&lt;p&gt;This feature is part of Github&#039;s Packages feature.  To manage your images you need to go to your
personal or organization&#039;s page and click on the &lt;code&gt;Packages&lt;/code&gt; tab.&lt;/p&gt;
&lt;p&gt;By default, packages are &amp;quot;Private&amp;quot;.  You can change this default by going to your account or org&#039;s
settings page, click on the &amp;quot;Packages&amp;quot; option on the side bar.  Under &amp;quot;Packages permissions&amp;quot; -&amp;gt;
&amp;quot;Package creation&amp;quot;, you can change the default visibility.&lt;/p&gt;
&lt;p&gt;In general, you can have a Github repo containing the source for a container image, and
through the use of Github Actions, you can automatically publish the container to the
GHCR.  From then on, you can pull the container image using docker engine.&lt;/p&gt;
&lt;p&gt;To enable this you need to create a &lt;code&gt;.github/workflows/docker-image.yml&lt;/code&gt; file with the contents:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/ghcr/workflow.png&quot; alt=&quot;workflow&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;Initialization&quot; name=&quot;Initialization&quot;&gt;Initialization&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: Docker Image CI

on:
  workflow_dispatch:
  push:
    branches: [ &quot;prerel&quot;, &quot;prerel-*&quot; ]
    tags: [ &quot;*&quot; ]
  pull_request:
    branches: [ &quot;main&quot; ]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These control events on when to run the workflow.  See &lt;a href=&quot;https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows&quot;&gt;Events that trigger workflows&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For rolling releases I would use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;on:
  workflow_dispatch:
  push:
    branches: [ &quot;main&quot;, &quot;prerel&quot;, &quot;prerel-*&quot;, &quot;bugfix*&quot; ]
  schedule:
  # Run every 8th of the month
  - cron: &quot;0 2 8 * *&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Basic+env+variables&quot; name=&quot;Basic+env+variables&quot;&gt;Basic env variables&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;
env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Declares some environmental variables.  Here we are indicating that we will be using
GHCR.  Also, we are using the repository name (owner/repo) for the image name.  Be careful
that the IMAGE_NAME should be all lowercase.&lt;/p&gt;
&lt;h2 id=&quot;Job+declaration&quot; name=&quot;Job+declaration&quot;&gt;Job declaration&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;jobs:

  build:

    runs-on: ubuntu-latest

    permissions:
      contents: read
      packages: write
      attestations: write
      id-token: write&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This starts the actual jobs to run.  The &lt;code&gt;permissions&lt;/code&gt; block is important as these
are needed to configure the automatic &lt;code&gt;GITHUB_TOKEN&lt;/code&gt;.  See
&lt;a href=&quot;https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/controlling-permissions-for-github_token&quot;&gt;Controlling permissions for GITHUB_TOKEN&lt;/a&gt; for details.&lt;/p&gt;
&lt;h2 id=&quot;Steps&quot; name=&quot;Steps&quot;&gt;Steps&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;    steps:&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Workflow requires the following steps:&lt;/p&gt;
&lt;h3 id=&quot;Run-time+additional+env+variables&quot; name=&quot;Run-time+additional+env+variables&quot;&gt;Run-time additional env variables&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;    - name: Additional environment
      run: |
        (
          echo &quot;IMAGE_NAME=$(echo &quot;${{ env.IMAGE_NAME }}&quot; | tr A-Z a-z)&quot;
          echo &quot;PKG_OWNER=$(echo &quot;${{ env.IMAGE_NAME}}&quot; | cut -d/ -f1)&quot;
          echo &quot;PKG_NAME=$(basename &quot;${{ env.IMAGE_NAME}}&quot;)&quot;
          echo &quot;BUILD_DATE=$(date +&#039;%Y%m%d&#039;)&quot;
        ) &amp;gt;&amp;gt; $GITHUB_ENV&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This makes sure that the &lt;code&gt;env.IMAGE_NAME&lt;/code&gt; is all lowercase.  This is a
docker client limitation.  It also splits the image name into
&lt;code&gt;env.PKG_OWNER&lt;/code&gt; and &lt;code&gt;env.PKG_NAME&lt;/code&gt;.  The &lt;code&gt;env.PKG_NAME&lt;/code&gt; is needed later by
the &lt;a href=&quot;https://github.com/actions/delete-package-versions&quot;&gt;actions/delete-package-versions&lt;/a&gt; action.&lt;/p&gt;
&lt;h3 id=&quot;Git+source+checkout&quot; name=&quot;Git+source+checkout&quot;&gt;Git source checkout&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;    - name: checkout repository
      uses: actions/checkout@v4&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is fairly standard.  Check out our source code.&lt;/p&gt;
&lt;h3 id=&quot;Additional+configuration&quot; name=&quot;Additional+configuration&quot;&gt;Additional configuration&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;    - name: Read additional environment from file
      uses: cosq-network/dotenv-loader@v1.0.2
      with:
        env-file: dotenv&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read additional environment variables from a file.  In this
example, &lt;code&gt;dotenv&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is an optional step.  I am using this to define certain variables instead of
hardcoding them into the Dockerfile.&lt;/p&gt;
&lt;h3 id=&quot;Log+in+to+GHCR&quot; name=&quot;Log+in+to+GHCR&quot;&gt;Log in to GHCR&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;    - name: Log in to the Container registry
      uses: docker/login-action@v3
      with:
        registry: ${{ env.REGISTRY }}
        username: ${{ github.actor }}
        password: ${{ secrets.GITHUB_TOKEN }}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This steps logs you into the GHCR so you are able to push images.  It uses
&lt;a href=&quot;https://github.com/docker/login-action&quot;&gt;docker/login-action&lt;/a&gt;.  It supports
to many container repositories such as docker hub.  Not just GHCR.&lt;/p&gt;
&lt;h3 id=&quot;Container+metadata&quot; name=&quot;Container+metadata&quot;&gt;Container metadata&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;    - name: Extract metadata (tags, labels) for Docker
      id: meta
      uses: docker/metadata-action@v5
      with:
        images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
        tags: |
          type=schedule
          type=ref,event=branch
          type=ref,event=tag
          type=ref,event=pr
          type=raw,value=&lt;img src=&quot;NacoWiki/web/albatros.php/posts/2025/date&quot; alt=&quot;date&quot; title=&quot;date&quot; &#039;YYYYMMDD&#039;&gt;      &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Gets meta data using &lt;a href=&quot;https://github.com/docker/metadata-action&quot;&gt;docker/metadata-action&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Tags are generated according to:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;Ref&lt;/th&gt;
&lt;th&gt;Docker Tags&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pull_request&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;refs/pull/2/merge&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pr-2&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;push&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;refs/heads/master&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;master&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;push&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;refs/heads/releases/v1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;releases-v1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;push tag&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;refs/tags/v1.2.3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v1.2.3&lt;/code&gt;, &lt;code&gt;latest&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;push tag&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;refs/tags/v2.0.8-beta.67&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v2.0.8-beta.67&lt;/code&gt;, &lt;code&gt;latest&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;workflow_dispatch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;refs/heads/master&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;master&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;In my example, I also generate a tag based on the date.&lt;/p&gt;
&lt;p&gt;Alternatively, for rolling releae I would use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;        tags: |
          type=raw,value=latest,enable={{ is_default_branch }}
          type=raw,value=&lt;img src=&quot;NacoWiki/web/albatros.php/posts/2025/date&quot; alt=&quot;date&quot; title=&quot;date&quot; &#039;YYYYMMDD&#039;&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Essentially, we are tagging by date, and the default branch also tags as &lt;code&gt;latest&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;Container+build+and+push&quot; name=&quot;Container+build+and+push&quot;&gt;Container build and push&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;    - name: Build and push Docker image
      id: push
      uses: docker/build-push-action@v3
      with:
        context: .
        push: true
        tags:  ${{ steps.meta.outputs.tags }}
        labels: ${{ steps.meta.outputs.labels }}
        build-args: |
          LIBC=${{ env.LIBC }}
          PYTHON_VERSION=${{ env.PYTHON_VERSION }}
          PYINSTALLER_VERSION=${{ env.PYINSTALLER_VERSION }}
          UPX_VERSION=${{ env.UPX_VERSION }}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this step, we create the image and push it to the GHCR.  It makes use of
&lt;a href=&quot;https://github.com/docker/build-push-action&quot;&gt;docker/build-push-action&lt;/a&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Tags make use of the output of &lt;a href=&quot;https://github.com/docker/metadata-action&quot;&gt;docker/metadata-action&lt;/a&gt;,
also make use of some pre-computed values from a previous step.&lt;/li&gt;
&lt;li&gt;We also pass configuration values to the &lt;code&gt;Dockerfile&lt;/code&gt; using &lt;code&gt;build-args&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;Artifact+attestation&quot; name=&quot;Artifact+attestation&quot;&gt;Artifact attestation&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;    - name: Generate artifact attestation
      uses: actions/attest-build-provenance@v2
      with:
        subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}
        subject-digest: ${{ steps.push.outputs.digest }}
        push-to-registry: true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Generates signed build provenance attestations for workflow artifacts.&lt;/p&gt;
&lt;p&gt;Artifact Attestations allow project maintainers to effortlessly create a tamper-proof,
unforgeable paper trail linking their software to the process which created it.&lt;/p&gt;
&lt;p&gt;For more details read
&lt;a href=&quot;https://github.blog/news-insights/product-news/introducing-artifact-attestations-now-in-public-beta/&quot;&gt;Introducing Artifact attestations&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;Cleaning+Container+registry&quot; name=&quot;Cleaning+Container+registry&quot;&gt;Cleaning Container registry&lt;/h3&gt;
&lt;p&gt;Storage isn&#039;t free and registries can often get bloated with unused images. Having a retention
policy to prevent clutter makes sense in most cases.&lt;/p&gt;
&lt;p&gt;In GCHR you can use the action &lt;a href=&quot;https://github.com/actions/delete-package-versions&quot;&gt;actions/delete-package-versions&lt;/a&gt;
to keep things tidy.&lt;/p&gt;
&lt;p&gt;Example usage:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;    - uses: actions/delete-package-versions@v5
      with:
        package-name: &quot;${{ env.PKG_NAME }}&quot;
        package-type: &#039;container&#039;
        min-versions-to-keep: 9
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This simply keeps only the last 9 images.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/ghcr/steps.png&quot; alt=&quot;steps&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;Using+the+image&quot; name=&quot;Using+the+image&quot;&gt;Using the image&lt;/h2&gt;
&lt;p&gt;Once the container image is build and published to the GCHR you can run it with a command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker run ghcr.io/pkgowner/imagename:latest&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>Using Excel files from Python</title>
<link href="https://www.0ink.net/posts/2025/2025-10-15-python-excel.html"></link>
<id>urn:uuid:95bfe581-eb0d-493d-e70f-ec09934a1767</id>
<updated>2025-05-05T00:00:00+02:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Intro
XlsxWriter
openpyxl
xlwings
pywin32
...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Intro&quot;&gt;Intro&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#XlsxWriter&quot;&gt;XlsxWriter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#openpyxl&quot;&gt;openpyxl&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#xlwings&quot;&gt;xlwings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#pywin32&quot;&gt;pywin32&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Conclusions&quot;&gt;Conclusions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Hints+and+Tips&quot;&gt;Hints and Tips&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#OpenPyxl&quot;&gt;OpenPyxl&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Data+Validation&quot;&gt;Data Validation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Creating+groups&quot;&gt;Creating groups&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#xlwings&quot;&gt;xlwings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Steps+to+Enable+Programmatic+Access+to+VBA&quot;&gt;Steps to Enable Programmatic Access to VBA&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Additional+Considerations&quot;&gt;Additional Considerations&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/py-excel/pyxl.png&quot; alt=&quot;py x excel&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;Intro&quot; name=&quot;Intro&quot;&gt;Intro&lt;/h2&gt;
&lt;p&gt;The other day I had to create a report in Excel using Python for work.&lt;/p&gt;
&lt;p&gt;Python has no shortage of libraries to create Excel files.  There are
portable solutions that would work on Windows and Linux, as well
as solutions that only run on Windows and depend on Excel being
available.&lt;/p&gt;
&lt;p&gt;They have their pros and ther cons.  This is a run down of
the possible options.&lt;/p&gt;
&lt;h2 id=&quot;XlsxWriter&quot; name=&quot;XlsxWriter&quot;&gt;XlsxWriter&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/py-excel/xlsxwriter-demo.png&quot; alt=&quot;xlsxwriter demo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The first option I tried was &lt;a href=&quot;https://github.com/jmcnamara/XlsxWriter&quot;&gt;XlsxWriter&lt;/a&gt;.  This solution is cross-platform,
being compatible with Linux and Windows.  It does not have many dependancies.
It however is limited to writing only and does not support creating VBA macros
in the created Excel files.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/jmcnamara/XlsxWriter&quot;&gt;XlsxWriter&lt;/a&gt; is a Python module for writing files in the Excel 2007+ XLSX file format.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/jmcnamara/XlsxWriter&quot;&gt;XlsxWriter&lt;/a&gt; can be used to write text, numbers, formulas and hyperlinks to multiple
worksheets and it supports features such as formatting and many more, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;100% compatible Excel XLSX files.&lt;/li&gt;
&lt;li&gt;Full formatting.&lt;/li&gt;
&lt;li&gt;Merged cells.&lt;/li&gt;
&lt;li&gt;Defined names.&lt;/li&gt;
&lt;li&gt;Charts.&lt;/li&gt;
&lt;li&gt;Autofilters.&lt;/li&gt;
&lt;li&gt;Data validation and drop down lists.&lt;/li&gt;
&lt;li&gt;Conditional formatting.&lt;/li&gt;
&lt;li&gt;Worksheet PNG/JPEG/GIF/BMP/WMF/EMF images.&lt;/li&gt;
&lt;li&gt;Rich multi-format strings.&lt;/li&gt;
&lt;li&gt;Cell comments.&lt;/li&gt;
&lt;li&gt;Integration with Pandas and Polars.&lt;/li&gt;
&lt;li&gt;Textboxes.&lt;/li&gt;
&lt;li&gt;Support for adding Macros.&lt;/li&gt;
&lt;li&gt;Memory optimization mode for writing large files.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It supports Python 3.4+ and PyPy3 and uses standard libraries only.&lt;/p&gt;
&lt;p&gt;It is ideal for generating reports and has a very pythonic API.&lt;/p&gt;
&lt;h2 id=&quot;openpyxl&quot; name=&quot;openpyxl&quot;&gt;openpyxl&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/py-excel/openpyxl-logo.png&quot; alt=&quot;openpyxl logo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This is the second option.  The main advantage over &lt;a href=&quot;https://github.com/jmcnamara/XlsxWriter&quot;&gt;XlsxWriter&lt;/a&gt; is that allows
reading existing xlsx files as well as writing.  Like &lt;a href=&quot;https://github.com/jmcnamara/XlsxWriter&quot;&gt;XlsxWriter&lt;/a&gt; it is a
portable library, able to run on Windows and Linux.  It does not have any
strange dependancies.  The main disadvantage I found is that it is a bit
temperamental, and the order of how you do things can impact the output
of the generated file.  The API specifically is a bit wonky with
things being non-intuitive.  Also, like &lt;a href=&quot;https://github.com/jmcnamara/XlsxWriter&quot;&gt;XlsxWriter&lt;/a&gt;, &lt;a href=&quot;https://foss.heptapod.net/openpyxl/openpyxl&quot;&gt;openpyxl&lt;/a&gt;
does not support reading or writing VBA macros.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://foss.heptapod.net/openpyxl/openpyxl&quot;&gt;openpyxl&lt;/a&gt; is a Python library to read/write Excel 2010 xlsx/xlsm/xltx/xltm files.&lt;/p&gt;
&lt;p&gt;It was born from lack of existing library to read/write natively from Python
the Office Open XML format.&lt;/p&gt;
&lt;p&gt;It was initially based on the &lt;a href=&quot;https://github.com/phpexcel/PHPExcel&quot;&gt;PHPExcel&lt;/a&gt; library.&lt;/p&gt;
&lt;p&gt;If you have to read and write Excel files on Linux as well as Windows, this
seems to be the way to go.  However, be prepared for a steep learning curve
and do a lot of trial and error to get things working right.&lt;/p&gt;
&lt;h2 id=&quot;xlwings&quot; name=&quot;xlwings&quot;&gt;xlwings&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/py-excel/xlwings.png&quot; alt=&quot;xlwings banner&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The third option is &lt;a href=&quot;https://www.xlwings.org/&quot;&gt;xlwings&lt;/a&gt;.  This is a &lt;em&gt;freemium&lt;/em&gt; library that has multiple
versions with different capabilities.  These are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.xlwings.org/en/latest/&quot;&gt;xlwings open source&lt;/a&gt; &lt;br /&gt;xlwings (Open Source) is a BSD-licensed Python library that makes it easy to call Python
from Excel and vice versa:
&lt;ul&gt;
&lt;li&gt;Scripting: Automate/interact with Excel from Python using a syntax close to VBA.&lt;/li&gt;
&lt;li&gt;Macros: Replace VBA macros with clean and powerful Python code.&lt;/li&gt;
&lt;li&gt;UDFs: Write User Defined Functions (UDFs) in Python (Windows only)&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.xlwings.org/en/0.24.2/pro.html&quot;&gt;xlwings pro&lt;/a&gt; &lt;br /&gt;xlwings pro adds these features:
&lt;ul&gt;
&lt;li&gt;One-click Installer: Easily build your own Python installer including all dependencies—your
end users don’t need to know anything about Python.&lt;/li&gt;
&lt;li&gt;Embedded code: Store your Python source code directly in Excel for easy deployment.&lt;/li&gt;
&lt;li&gt;xlwings Reports: A template-based reporting mechanism, allowing business users to change
the layout of the report without having to touch the Python code.&lt;/li&gt;
&lt;li&gt;Markdown Formatting: Support for Markdown formatting of text in cells and shapes like e.g.,
text boxes.&lt;/li&gt;
&lt;li&gt;Permissioning of Code Execution: Control which users can run which Python modules via xlwings.&lt;/li&gt;
&lt;li&gt;Table.update(): An easy way to keep an Excel table in sync with a pandas DataFrame&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lite.xlwings.org/&quot;&gt;xlwings lite&lt;/a&gt; &lt;br /&gt;xlwings Lite brings the VBA experience into the modern age by offering a privacy-first,
secure, and developer-friendly way to automate Excel and write custom functions with Python.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://server.xlwings.org/en/latest/&quot;&gt;xlwings server&lt;/a&gt; &lt;br /&gt;xlwings Server adds Python support to Microsoft Excel and Google Sheets without the need of
a local Python installation. xlwings Server is self-hosted and runs on any platform that
supports Python or Docker, including bare-metal servers, Linux-based VMs, Docker Compose,
Kubernetes and serverless products like Azure functions or AWS Lambda.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For the simple use case of reading and writing Excel file data it is a bit of an overkill.
It does support adding VBA code to Excel files, however there are limitations associated
with the VBA object model.  So while code can be added, classes and expred modules
can not.&lt;/p&gt;
&lt;h2 id=&quot;pywin32&quot; name=&quot;pywin32&quot;&gt;pywin32&lt;/h2&gt;
&lt;p&gt;You can always use the &lt;a href=&quot;https://github.com/mhammond/pywin32&quot;&gt;Python for Win32&lt;/a&gt; with COM support.  This gives
you access to the Component Object Model (COM), which is the native automation
functionality of Windows which Excel supports.&lt;/p&gt;
&lt;p&gt;This is a quick example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import win32com.client
import sys, io

# Open up Excel and make it visible (actually you don&#039;t need to make it visible)
excel = win32com.client.Dispatch(&#039;Excel.Application&#039;)
excel.Visible = True

# Redirect the stdout to a file
orig_stdout = sys.stdout
bk = io.open(&quot;Answers_Report.txt&quot;, mode=&quot;w&quot;, encoding=&quot;utf-8&quot;)
sys.stdout = bk

# Select a file and open it
file = &quot;path_of_file&quot;
wb_data = excel.Workbooks.Open(file)

# Get the answers to the Q1A and write them into the summary file
mission=wb_data.Worksheets(&quot;1ayb_MisiónyVisiónFutura&quot;).Range(&quot;C6&quot;)
vision =wb_data.Worksheets(&quot;1ayb_MisiónyVisiónFutura&quot;).Range(&quot;C7&quot;)
print(&quot;Question 1A&quot;)
print(&quot;Mission:&quot;,mission)
print(&quot;Vision:&quot; ,vision)
print()&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Conclusions&quot; name=&quot;Conclusions&quot;&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;For strictly writing Excel files, &lt;a href=&quot;https://github.com/jmcnamara/XlsxWriter&quot;&gt;XlsxWriter&lt;/a&gt; is more than enough.  It is easier
to use and have a nice Pythonic API.&lt;/p&gt;
&lt;p&gt;For reading and writing Excel files, &lt;a href=&quot;https://foss.heptapod.net/openpyxl/openpyxl&quot;&gt;openpyxl&lt;/a&gt; is enough, despite its flaws.&lt;/p&gt;
&lt;p&gt;If you need limited support for VBA code, &lt;a href=&quot;https://www.xlwings.org/&quot;&gt;xlwings&lt;/a&gt; is the best option.&lt;/p&gt;
&lt;h2 id=&quot;Hints+and+Tips&quot; name=&quot;Hints+and+Tips&quot;&gt;Hints and Tips&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/py-excel/hints-tips.png&quot; alt=&quot;hings and tips&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;OpenPyxl&quot; name=&quot;OpenPyxl&quot;&gt;OpenPyxl&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://foss.heptapod.net/openpyxl/openpyxl&quot;&gt;openpyxl&lt;/a&gt; has a lot of quirks.  Here is a number of things I discovered:&lt;/p&gt;
&lt;h4 id=&quot;Data+Validation&quot; name=&quot;Data+Validation&quot;&gt;Data Validation&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/py-excel/openpyxl-datavalidation.png&quot; alt=&quot;openpyxl data validation&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This is documented in
&lt;a href=&quot;https://openpyxl.readthedocs.io/en/latest/validation.html&quot;&gt;openpyxl Data Validation&lt;/a&gt; and
&lt;a href=&quot;https://openpyxl.readthedocs.io/en/stable/api/openpyxl.worksheet.datavalidation.html&quot;&gt;data validation module&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Data validators can be applied to ranges of cells but are not enforced or evaluated. Ranges
do not have to be contiguous: eg. “A1 B2:B5” is contains A1 and the cells B2 to B5 but not A2
or B2.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;
# Create a data-validation object with list validation
dv = DataValidation(type=&quot;list&quot;, formula1=&#039;&quot;Dog&quot;,&quot;Cat&quot;,&quot;Bat&quot;&#039;, allow_blank=True)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;formula1&lt;/code&gt; is a formula that can contain a list.  The list is comma separated.
You can use double quotes if you need to escape values with commas.  Double quotes can
be escaped by entering two double quotes.   (i.e. in general, it folows Excel formula
syntax).&lt;/p&gt;
&lt;p&gt;The other strange thing is that if you want to show a drop down box with the list options
you can add the keyword argument &lt;code&gt;showDropDown&lt;/code&gt;.  If you want to &lt;em&gt;see&lt;/em&gt; the dropdown, set
this to &lt;strong&gt;False&lt;/strong&gt;.  If you want to hide it, set it to &lt;strong&gt;True&lt;/strong&gt;.&lt;/p&gt;
&lt;h4 id=&quot;Creating+groups&quot; name=&quot;Creating+groups&quot;&gt;Creating groups&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/py-excel/openpyxl-groups.png&quot; alt=&quot;openpyxl groups&quot; /&gt;&lt;/p&gt;
&lt;p&gt;For creating groups you can use&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;  worksheet.column_dimensions.group(start, end, **kwargs)
  worksheet.row_dimensions.group(start, end, **kwargs)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Keyword args:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;hidden : hide group&lt;/li&gt;
&lt;li&gt;outline_level : for multilevel groupings&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There a further restrictions with creating groups, in that:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;you must create the cells in the groups before grouping&lt;/li&gt;
&lt;li&gt;if you are using multi-level groups, you must create the outer-group first
before creating the inter groups.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;xlwings&quot; name=&quot;xlwings&quot;&gt;xlwings&lt;/h3&gt;
&lt;p&gt;Some tips when using &lt;a href=&quot;https://www.xlwings.org/&quot;&gt;xlwings&lt;/a&gt;:&lt;/p&gt;
&lt;h3 id=&quot;Steps+to+Enable+Programmatic+Access+to+VBA&quot; name=&quot;Steps+to+Enable+Programmatic+Access+to+VBA&quot;&gt;Steps to Enable Programmatic Access to VBA&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Open Excel&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;Launch Excel (you don&#039;t need to open a specific workbook).&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Access Options&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;Click on &lt;code&gt;File&lt;/code&gt; in the top menu.&lt;/li&gt;
&lt;li&gt;Select &lt;code&gt;Options&lt;/code&gt; from the menu to open the Excel Options dialog.
&lt;code&gt;Options&lt;/code&gt; item should be at the bottom of the menu.&lt;/li&gt;
&lt;li&gt;&lt;img src=&quot;/images/2025/py-excel/menu.png&quot; alt=&quot;excel menu&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Trust Center&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;In the Excel Options dialog, click on &lt;code&gt;Trust Center&lt;/code&gt; in the left pane.&lt;/li&gt;
&lt;li&gt;Click on &lt;code&gt;Trust Center Settings...&lt;/code&gt; to open the Trust Center settings.&lt;/li&gt;
&lt;li&gt;&lt;img src=&quot;/images/2025/py-excel/trustcenter.png&quot; alt=&quot;excel menu&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Macro Settings&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;In the Trust Center, click on &lt;code&gt;Macro Settings&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Check the option labeled &amp;quot;Trust access to the VBA project object model&amp;quot;.&lt;/li&gt;
&lt;li&gt;&lt;img src=&quot;/images/2025/py-excel/macrosettings.png&quot; alt=&quot;excel menu&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Save Settings&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;Click &lt;code&gt;OK&lt;/code&gt; to save the Trust Center settings.&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;OK&lt;/code&gt; again to exit the Excel Options dialog.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;Additional+Considerations&quot; name=&quot;Additional+Considerations&quot;&gt;Additional Considerations&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Security Implications&lt;/strong&gt;: Enabling programmatic access to the VBA
project object model can expose your system to potential security
risks, especially if you run untrusted code. Ensure that the code
you execute is from a trusted source.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IT Policies&lt;/strong&gt;: If you are using Excel in a corporate environment,
these settings might be managed by IT policies, and you may need
to contact your IT department for assistance.&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Using Podman on Alpine Linux</title>
<link href="https://www.0ink.net/posts/2025/2025-10-01-podman.html"></link>
<id>urn:uuid:94ba1e28-bdca-8e0b-ceeb-df1dac564b55</id>
<updated>2025-04-01T00:00:00+02:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Introduction
What is Podman?
Installation on Alpine Linux

Rootful vs Rootless
...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Introduction&quot;&gt;Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#What+is+Podman%3F&quot;&gt;What is Podman?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Installation+on+Alpine+Linux&quot;&gt;Installation on Alpine Linux&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Rootful+vs+Rootless&quot;&gt;Rootful vs Rootless&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Configuring+Podman&quot;&gt;Configuring Podman&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Compatibility+with+Docker&quot;&gt;Compatibility with Docker&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Moving+of+--link%27ed+containers&quot;&gt;Moving of --link&#039;ed containers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/podman/podman-logo-full-vert.png&quot; alt=&quot;podman logo&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;Introduction&quot; name=&quot;Introduction&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;I&#039;ve been using &lt;a href=&quot;https://docs.docker.com/get-started/get-docker/&quot;&gt;Docker&lt;/a&gt; for a few years, and recently decided to
switch to &lt;a href=&quot;https://podman.io/&quot;&gt;Podman&lt;/a&gt; for a new server I&#039;m building. I was pleasantly
surprised by how quick and easy the transition was.&lt;/p&gt;
&lt;h2 id=&quot;What+is+Podman%3F&quot; name=&quot;What+is+Podman%3F&quot;&gt;What is Podman?&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/podman/confused-seal.png&quot; alt=&quot;confused sea&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://podman.io/&quot;&gt;Podman&lt;/a&gt; is an alternative to &lt;a href=&quot;https://docs.docker.com/get-started/get-docker/&quot;&gt;Docker Desktop&lt;/a&gt;.&lt;br /&gt;
&lt;a href=&quot;https://podman.io/&quot;&gt;Podman&lt;/a&gt; is an open source container management tool designed to provide
a daemonless and rootless approach to running containers, setting it apart from
traditional container platforms like &lt;a href=&quot;https://docs.docker.com/get-started/get-docker/&quot;&gt;Docker&lt;/a&gt;. It allows users to manage
containers, pods, and images using a command-line interface similar to Docker&#039;s,
making it accessible for those familiar with &lt;a href=&quot;https://docs.docker.com/get-started/get-docker/&quot;&gt;Docker&lt;/a&gt; commands. &lt;a href=&quot;https://podman.io/&quot;&gt;Podman&lt;/a&gt;
is particularly focused on security, enabling containers to run without requiring
root privileges, which helps minimize potential vulnerabilities. It integrates
seamlessly with tools like Kubernetes, supporting native pod management. Developed
as part of the Red Hat ecosystem, &lt;a href=&quot;https://podman.io/&quot;&gt;Podman&lt;/a&gt; is a versatile choice for users
seeking a secure, lightweight, and flexible alternative for container management.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://podman.io/&quot;&gt;Podman&lt;/a&gt; bills itself
as an open source alternative to &lt;a href=&quot;https://docs.docker.com/get-started/get-docker/&quot;&gt;Docker&lt;/a&gt;, despite &lt;a href=&quot;https://docs.docker.com/get-started/get-docker/&quot;&gt;Docker&lt;/a&gt;
also being open source.
Docker Inc., the company behind Docker, has a business model that includes offering
commercial products and services around its open source technology.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://podman.io/&quot;&gt;Podman&lt;/a&gt; is an alternative to &lt;a href=&quot;https://docs.docker.com/get-started/get-docker/&quot;&gt;Docker Desktop&lt;/a&gt;. It&#039;s an open-source
container management tool that offers a daemonless and rootless approach to
running containers, differentiating it from traditional platforms like
&lt;a href=&quot;https://docs.docker.com/get-started/get-docker/&quot;&gt;Docker&lt;/a&gt;. Podman allows users to manage containers, pods, and images
using a command-line interface similar to &lt;a href=&quot;https://docs.docker.com/get-started/get-docker/&quot;&gt;Docker&lt;/a&gt;, making it
user-friendly for those familiar with &lt;a href=&quot;https://docs.docker.com/get-started/get-docker/&quot;&gt;Docker&lt;/a&gt; commands. It emphasizes
security by enabling containers to run without root privileges, reducing
potential vulnerabilities. &lt;a href=&quot;https://podman.io/&quot;&gt;Podman&lt;/a&gt; integrates well with Kubernetes,
supporting native pod management, and is developed as part of the Red Hat
ecosystem. It&#039;s a versatile option for users looking for a secure, lightweight,
and flexible alternative for container management.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/podman/docker-logo-blue-lo.png&quot; alt=&quot;docker logo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;While &lt;a href=&quot;https://podman.io/&quot;&gt;Podman&lt;/a&gt; promotes itself as an open-source alternative to
&lt;a href=&quot;https://docs.docker.com/get-started/get-docker/&quot;&gt;Docker&lt;/a&gt;, it&#039;s worth noting that Docker is also open source. Docker
Inc. offers commercial products and services around its open-source technology.&lt;/p&gt;
&lt;h2 id=&quot;Installation+on+Alpine+Linux&quot; name=&quot;Installation+on+Alpine+Linux&quot;&gt;Installation on Alpine Linux&lt;/h2&gt;
&lt;p&gt;Since my preferred server&#039;s operating system is &lt;a href=&quot;https://alpinelinux.org/&quot;&gt;Alpine Linux&lt;/a&gt;, I
installed &lt;a href=&quot;https://podman.io/&quot;&gt;Podman&lt;/a&gt; on a test &lt;a href=&quot;https://alpinelinux.org/&quot;&gt;Alpine Linux&lt;/a&gt; virtual machine.&lt;/p&gt;
&lt;p&gt;First, enable the &lt;a href=&quot;https://alpinelinux.org/&quot;&gt;Alpine Linux&lt;/a&gt; community repository by modifying the
&lt;code&gt;/etc/apk/repositories&lt;/code&gt; file and uncommenting the following line:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;http://dl-cdn.alpinelinux.org/alpine/vX.YY/community&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, install &lt;a href=&quot;https://podman.io/&quot;&gt;Podman&lt;/a&gt; with the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;apk add podman&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To enable Podman at startup, use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;rc-update add podman&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/podman/seal-diving.png&quot; alt=&quot;seal diving&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This is all that&#039;s needed to run Podman in rootful mode.&lt;/p&gt;
&lt;h3 id=&quot;Rootful+vs+Rootless&quot; name=&quot;Rootful+vs+Rootless&quot;&gt;Rootful vs Rootless&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://podman.io/&quot;&gt;Podman&lt;/a&gt; supports two modes of operation: rootful and rootless. In
rootful mode, the container runs as root on the Linux host (or VM on
Mac/Windows), while in rootless mode, it runs under a standard Unix user
account. Rootless mode offers stronger security, but some containers may not
work under the increased restrictions. For example, containers that create new
devices or perform restricted operations must run as root. This is different from
the &lt;em&gt;USER&lt;/em&gt; value in Containerfile/Dockerfile, which affects how processes inside
the container perceive themselves. In rootless mode, processes that appear as root
inside the container are actually running as a restricted user on the host system.&lt;/p&gt;
&lt;p&gt;Since I&#039;m migrating from &lt;a href=&quot;https://docs.docker.com/get-started/get-docker/&quot;&gt;Docker&lt;/a&gt;, I&#039;ve decided to keep everything in
rootful mode.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://wiki.alpinelinux.org/wiki/Podman&quot;&gt;Alpine Linux wiki&lt;/a&gt; provides steps for installing &lt;a href=&quot;https://podman.io/&quot;&gt;Podman&lt;/a&gt; in
rootless mode. Note that the instructions for running as &lt;code&gt;root&lt;/code&gt; are slightly outdated.&lt;/p&gt;
&lt;h3 id=&quot;Configuring+Podman&quot; name=&quot;Configuring+Podman&quot;&gt;Configuring Podman&lt;/h3&gt;
&lt;p&gt;I usually run my servers in &lt;a href=&quot;https://wiki.alpinelinux.org/wiki/Diskless_Mode&quot;&gt;diskless&lt;/a&gt; and/or &lt;a href=&quot;https://wiki.alpinelinux.org/wiki/Data_Disk_Mode&quot;&gt;data disk&lt;/a&gt; modes. Because
&lt;a href=&quot;https://podman.io/&quot;&gt;Podman&lt;/a&gt; expects some data to be persistent, I modify the configuration
to store persistent data elsewhere:&lt;/p&gt;
&lt;p&gt;Ensure &lt;a href=&quot;https://podman.io/&quot;&gt;Podman&lt;/a&gt; is &lt;strong&gt;not&lt;/strong&gt; running:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;service podman stop&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Edit &lt;code&gt;/etc/containers/storage.conf&lt;/code&gt;, find the &lt;code&gt;[storage]&lt;/code&gt; section,
and modify the &lt;code&gt;graphroot&lt;/code&gt; key to point to your persistent storage location:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;graphroot = &quot;/media/data/containers/podman&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make sure that the &lt;code&gt;graphroot&lt;/code&gt; directory exists:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir -p /media/data/containers/podman&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you stopped &lt;a href=&quot;https://podman.io/&quot;&gt;Podman&lt;/a&gt; earlier, you can start it now:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;service podman start&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Compatibility+with+Docker&quot; name=&quot;Compatibility+with+Docker&quot;&gt;Compatibility with Docker&lt;/h2&gt;
&lt;p&gt;For composer compatibility you can use &lt;a href=&quot;https://github.com/containers/podman-compose&quot;&gt;Podman Compose&lt;/a&gt; or
&lt;a href=&quot;https://github.com/docker/compose&quot;&gt;docker compose&lt;/a&gt; itself (with the previously mentioned
&lt;a href=&quot;https://docs.docker.com/reference/api/engine/&quot;&gt;socket API&lt;/a&gt; compatibility).  I myself chose to use
&lt;a href=&quot;https://github.com/docker/compose&quot;&gt;docker compose&lt;/a&gt; instead of &lt;a href=&quot;https://github.com/containers/podman-compose&quot;&gt;Podman Compose&lt;/a&gt; becase
&lt;a href=&quot;https://github.com/containers/podman-compose&quot;&gt;Podman Compose&lt;/a&gt; depends on Python (and I wanted to keep
my install leaner).&lt;/p&gt;
&lt;p&gt;To enable this you need to install &lt;a href=&quot;https://github.com/docker/compose&quot;&gt;docker compose&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://podman.io/&quot;&gt;Podman&lt;/a&gt; includes a socket &lt;a href=&quot;https://docs.docker.com/reference/api/engine/&quot;&gt;API&lt;/a&gt; that&#039;s drop-in compatible with
&lt;a href=&quot;https://docs.docker.com/get-started/get-docker/&quot;&gt;Docker&lt;/a&gt; tools, allowing you to use the &lt;a href=&quot;https://docs.docker.com/get-started/get-docker/&quot;&gt;Docker&lt;/a&gt; CLI,
&lt;a href=&quot;https://github.com/docker/compose&quot;&gt;docker-compose&lt;/a&gt;, and even &lt;a href=&quot;https://github.com/portainer/portainer&quot;&gt;Portainer&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/podman/portainer-screen-lo.png&quot; alt=&quot;portainer screen&quot; /&gt;&lt;/p&gt;
&lt;p&gt;For compatibility with compose tools, you can use &lt;a href=&quot;https://github.com/containers/podman-compose&quot;&gt;Podman Compose&lt;/a&gt; or
&lt;a href=&quot;https://github.com/docker/compose&quot;&gt;docker-compose&lt;/a&gt; itself, thanks to the socket &lt;a href=&quot;https://docs.docker.com/reference/api/engine/&quot;&gt;API&lt;/a&gt; compatibility.
I&#039;ve chosen to use &lt;a href=&quot;https://github.com/docker/compose&quot;&gt;docker-compose&lt;/a&gt; instead of &lt;a href=&quot;https://github.com/containers/podman-compose&quot;&gt;Podman Compose&lt;/a&gt;
because the latter depends on Python, and I wanted to keep my installation
leaner.&lt;/p&gt;
&lt;p&gt;To enable this, install &lt;a href=&quot;https://github.com/docker/compose&quot;&gt;docker-compose&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;apk add docker-cli-compose&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For convenience, create a small script at &lt;code&gt;/usr/local/bin/podman-compose&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/sh
# Set DOCKER_HOST pass -H to override
export DOCKER_HOST=unix:///run/podman/podman.sock
exec docker-compose &quot;$@&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using the socket &lt;a href=&quot;https://docs.docker.com/reference/api/engine/&quot;&gt;API&lt;/a&gt;, you can manage &lt;a href=&quot;https://podman.io/&quot;&gt;Podman&lt;/a&gt; with &lt;a href=&quot;https://docs.docker.com/get-started/get-docker/&quot;&gt;Docker&lt;/a&gt;
commands via the &lt;code&gt;-H&lt;/code&gt; option or the &lt;code&gt;DOCKER_HOST&lt;/code&gt; environment variable. For more
information, see the &lt;a href=&quot;https://docs.docker.com/reference/cli/dockerd/#daemon-socket-option&quot;&gt;Docker manual&lt;/a&gt;. On the &lt;a href=&quot;https://podman.io/&quot;&gt;Podman&lt;/a&gt;
host, you can use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-H unix:///run/podman/podman.sock&lt;/code&gt; or&lt;/li&gt;
&lt;li&gt;&lt;code&gt;export DOCKER_HOST=unix:///run/podman/podman.sock&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Interestingly, you can control a remote &lt;a href=&quot;https://podman.io/&quot;&gt;Podman&lt;/a&gt; instance using this method. For
example, you can use the &lt;a href=&quot;https://docs.docker.com/get-started/get-docker/&quot;&gt;Docker CLI&lt;/a&gt; on your development PC to control
the &lt;a href=&quot;https://podman.io/&quot;&gt;Podman&lt;/a&gt; instance on your server:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-H ssh://root@remote-server:22/run/podman/podman.sock&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;export DOCKER_HOST=ssh://root@remote-server/run/podman/podman.sock&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Moving+of+--link%27ed+containers&quot; name=&quot;Moving+of+--link%27ed+containers&quot;&gt;Moving of --link&#039;ed containers&lt;/h3&gt;
&lt;p&gt;Old containers using &lt;a href=&quot;https://docs.docker.com/engine/network/links/&quot;&gt;Container Links&lt;/a&gt; can not be moved directly to
&lt;a href=&quot;https://podman.io/&quot;&gt;Podman&lt;/a&gt; without changes.  The &lt;a href=&quot;https://docs.docker.com/engine/network/links/&quot;&gt;Container Links&lt;/a&gt; feature is
now obsolete and &lt;a href=&quot;https://docs.docker.com/get-started/get-docker/&quot;&gt;Docker&lt;/a&gt; supports it only for compatibility.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/podman/container-links.png&quot; alt=&quot;links&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In &lt;a href=&quot;https://podman.io/&quot;&gt;Podman&lt;/a&gt; you are better off creating a new network using:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;podman create network NETWORK_NAME&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Connect your containers to that network:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;podman run -d \
    -p 8080:80 \
    --restart=always \
    --network=NETWORK_NAME \
    nginx:latest&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then you can use the container name using the DNS resolver.  Note that the default
&lt;a href=&quot;https://podman.io/&quot;&gt;Podman&lt;/a&gt; network has this internal DNS resolver disabled and you need
to create a new one.&lt;/p&gt;
&lt;p&gt;If you are using &lt;code&gt;docker compose&lt;/code&gt; a network is created by default and all the
containers in the stack will be connected to it.&lt;/p&gt;</content>
</entry>
<entry>
<title>AdGuard Home</title>
<link href="https://www.0ink.net/posts/2025/2025-09-15-adguardhome.html"></link>
<id>urn:uuid:2c706131-4039-2b5d-2321-456bc31fe2dd</id>
<updated>2025-03-12T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
How does AdGuard Home compare to traditional ad blockers
Known limitations
Installation
Configuration
Reverse Proxy
...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#How+does+AdGuard+Home+compare+to+traditional+ad+blockers&quot;&gt;How does AdGuard Home compare to traditional ad blockers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Known+limitations&quot;&gt;Known limitations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Installation&quot;&gt;Installation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Configuration&quot;&gt;Configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Reverse+Proxy&quot;&gt;Reverse Proxy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Using+it&quot;&gt;Using it&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/adguardhome/adguard_home_logo-white.png&quot; alt=&quot;icon&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://adguard.com/en/adguard-home/overview.html&quot;&gt;AdGuard Home&lt;/a&gt; is a network-wide ads &amp;amp; trackers blocking DNS server.&lt;/p&gt;
&lt;p&gt;After set-up, it will cover &lt;em&gt;all&lt;/em&gt; your home devices, and you don’t need any client-side
software for that.&lt;/p&gt;
&lt;p&gt;With the rise of Internet-Of-Things and connected devices, it becomes more and more
important to be able to control your whole network.&lt;/p&gt;
&lt;p&gt;It operates as a DNS server that re-routes tracking domains to a “black hole”, thus preventing
your devices from connecting to those servers.&lt;/p&gt;
&lt;p&gt;As a network-wide ad-blocker, &lt;a href=&quot;https://adguard.com/en/adguard-home/overview.html&quot;&gt;AdGuard Home&lt;/a&gt; competes with &lt;a href=&quot;https://pi-hole.net/&quot;&gt;Pi-hole&lt;/a&gt;.  Here is a
comparison table between the two:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;AdGuard Home&lt;/th&gt;
&lt;th&gt;Pi-hole&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Platform Support&lt;/td&gt;
&lt;td&gt;Windows, macOS, Linux, Raspberry Pi, Docker&lt;/td&gt;
&lt;td&gt;Linux, Raspberry Pi, Docker&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DNS-over-HTTPS/TLS&lt;/td&gt;
&lt;td&gt;Built-in support&lt;/td&gt;
&lt;td&gt;Requires additional setup (using third-party tools)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Filtering Rules Syntax&lt;/td&gt;
&lt;td&gt;AdGuard-specific syntax; supports more complex rules&lt;/td&gt;
&lt;td&gt;Standard DNS filtering rules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Web Interface&lt;/td&gt;
&lt;td&gt;Modern, user-friendly interface&lt;/td&gt;
&lt;td&gt;Simple, functional interface&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Block Page Customization&lt;/td&gt;
&lt;td&gt;Limited customization&lt;/td&gt;
&lt;td&gt;More customizable block page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parental Control&lt;/td&gt;
&lt;td&gt;Built-in support for parental control features&lt;/td&gt;
&lt;td&gt;Lacks built-in parental control features&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Regular Expressions&lt;/td&gt;
&lt;td&gt;Supports regular expressions in filters&lt;/td&gt;
&lt;td&gt;Supports regular expressions in filters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automatic Updates&lt;/td&gt;
&lt;td&gt;No built-in automatic updates&lt;/td&gt;
&lt;td&gt;Built-in automatic updates for blocklists and core components&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Community Support&lt;/td&gt;
&lt;td&gt;Growing community&lt;/td&gt;
&lt;td&gt;Larger, well-established community&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;As you can see, there is no clear winner.  Most people would choose &lt;a href=&quot;https://pi-hole.net/&quot;&gt;Pi-Hole&lt;/a&gt;
as it has a larger, more well-established community. I opted for &lt;a href=&quot;https://adguard.com/en/adguard-home/overview.html&quot;&gt;AdGuard Home&lt;/a&gt;
because it has a simpler installation process due to very minimal dependancies.&lt;/p&gt;
&lt;p&gt;I am using &lt;a href=&quot;https://thekelleys.org.uk/dnsmasq/doc.html&quot;&gt;dnsmasq&lt;/a&gt; for my DNS and DHCP needs.  It is quite flexible and configurable.
There is some things that I am addressing with &lt;a href=&quot;https://adguard.com/en/adguard-home/overview.html&quot;&gt;AdGuard Home&lt;/a&gt; that I was
never able to address with &lt;a href=&quot;https://thekelleys.org.uk/dnsmasq/doc.html&quot;&gt;dnsmasq&lt;/a&gt; itself.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ad Blocking:  While it is possible to download block lists and add them
to &lt;a href=&quot;https://thekelleys.org.uk/dnsmasq/doc.html&quot;&gt;dnsmasq&lt;/a&gt;, this has to be done manually and it was difficult
to keep up to date.&lt;/li&gt;
&lt;li&gt;Reporting and stats: &lt;a href=&quot;https://thekelleys.org.uk/dnsmasq/doc.html&quot;&gt;dnsmasq&lt;/a&gt; is able to log DNS queries, you
have to create repots and generate statistics on your own.&lt;/li&gt;
&lt;li&gt;DNS interface: &lt;a href=&quot;https://thekelleys.org.uk/dnsmasq/doc.html&quot;&gt;dnsmasq&lt;/a&gt; lacks this completely.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://adguard.com/en/adguard-home/overview.html&quot;&gt;AdGuard Home&lt;/a&gt; does not cover all the functionality that &lt;a href=&quot;https://thekelleys.org.uk/dnsmasq/doc.html&quot;&gt;dnsmasq&lt;/a&gt; provides
such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;tftp for PXE booting&lt;/li&gt;
&lt;li&gt;No DHCP configuration for IPv6 (at least I couldn&#039;t find instructions for it)&lt;/li&gt;
&lt;li&gt;CLI integration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At the end, I am using &lt;a href=&quot;https://adguard.com/en/adguard-home/overview.html&quot;&gt;AdGuard Home&lt;/a&gt; and &lt;a href=&quot;https://thekelleys.org.uk/dnsmasq/doc.html&quot;&gt;dnsmasq&lt;/a&gt; in combination.&lt;/p&gt;
&lt;h2 id=&quot;How+does+AdGuard+Home+compare+to+traditional+ad+blockers&quot; name=&quot;How+does+AdGuard+Home+compare+to+traditional+ad+blockers&quot;&gt;How does AdGuard Home compare to traditional ad blockers&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/adguardhome/adguard_home.svg&quot; alt=&quot;icon&quot; /&gt;&lt;/p&gt;
&lt;p&gt;It depends.&lt;/p&gt;
&lt;p&gt;DNS sinkholing is capable of blocking a big percentage of ads, but it lacks the
flexibility and the power of traditional ad blockers. You can get a good impression about
the difference between these methods by reading &lt;a href=&quot;https://adguard.com/blog/adguard-vs-adaway-dns66.html&quot;&gt;this article&lt;/a&gt;, which compares
AdGuard for Android (a traditional ad blocker) to hosts-level ad blockers (which are almost
identical to DNS-based blockers in their capabilities). This level of protection is enough
for some users.&lt;/p&gt;
&lt;p&gt;Additionally, using a DNS-based blocker can help to block ads, tracking and analytics requests
on other types of devices, such as SmartTVs, smart speakers or other kinds of IoT devices
(on which you can&#039;t install traditional ad blockers).&lt;/p&gt;
&lt;h2 id=&quot;Known+limitations&quot; name=&quot;Known+limitations&quot;&gt;Known limitations&lt;/h2&gt;
&lt;p&gt;Here are some examples of what cannot be blocked by a DNS-level blocker:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;YouTube, Twitch ads;&lt;/li&gt;
&lt;li&gt;Facebook, Twitter, Instagram sponsored posts.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Essentially, any advertising that shares a domain with content cannot be blocked by a
DNS-level blocker.&lt;/p&gt;
&lt;p&gt;Is there a chance to handle this in the future?  DNS will never be enough to do this. Our only
option is to use a content blocking proxy like the standalone AdGuard applications.&lt;/p&gt;
&lt;h2 id=&quot;Installation&quot; name=&quot;Installation&quot;&gt;Installation&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://adguard.com/en/adguard-home/overview.html&quot;&gt;AdGuard Home&lt;/a&gt; is hosted on &lt;a href=&quot;https://github.com/AdguardTeam/AdGuardHome&quot;&gt;github&lt;/a&gt;.  You can download a pre-build
binary from its &lt;a href=&quot;https://github.com/AdguardTeam/AdGuardHome/releases&quot;&gt;releases page&lt;/a&gt;.  Since &lt;a href=&quot;https://adguard.com/en/adguard-home/overview.html&quot;&gt;AdGuard Home&lt;/a&gt; is developed
in Go language, its binaries seem to be fairly portable.&lt;/p&gt;
&lt;p&gt;Installing on Alpine Linux is fairly simple.  I am using an Ansible playbook for this,
but the process is as follows:&lt;/p&gt;
&lt;ol start=&quot;0&quot;&gt;
&lt;li&gt;I am using &lt;a href=&quot;https://thekelleys.org.uk/dnsmasq/doc.html&quot;&gt;dnsmasq&lt;/a&gt; with &lt;a href=&quot;https://adguard.com/en/adguard-home/overview.html&quot;&gt;AdGuard Home&lt;/a&gt;.  So the pre-requisite
step is to change the &lt;a href=&quot;https://thekelleys.org.uk/dnsmasq/doc.html&quot;&gt;dnsmasq&lt;/a&gt; so that it does &lt;em&gt;not&lt;/em&gt; listen on port 53/UDP.
This is done with the line:
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;port=5353&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Download binary from the &lt;a href=&quot;https://github.com/AdguardTeam/AdGuardHome/releases&quot;&gt;releases page&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Unpack the binary into your destination directory.  I use &lt;code&gt;/usr/local/lib/AdGuardHome&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://adguard.com/en/adguard-home/overview.html&quot;&gt;AdGuard Home&lt;/a&gt; reads configuration and saves some data in its installation
directory.  I relocate these via symbolic links:
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;agh_home=/usr/local/lib/AdGuardHome
ln -s /etc/AdGuardHome.yaml ${agh_home}/AdGuardHome.yaml
ln -s /var/lib/AdGuardHome ${agh_home}/data&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Create the start|stop scripts.  I am using:
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/sh
# Startup script
appdir=/usr/local/lib/AdGuardHome
mkdir -p &quot;$(readlink -f &quot;$appdir/data&quot;)&quot;
chmod 0700 &quot;$(readlink -f &quot;$appdir/data&quot;)&quot;
if [ ! -f /etc/AdGuardHome.yaml ] ; then
  cp /etc/AdGuardHome.proto /etc/AdGuardHome.yaml
elif [ /etc/AdGuardHome.proto -nt /etc/AdGuardHome.yaml ] ; then
  cp /etc/AdGuardHome.proto /etc/AdGuardHome.yaml
fi
$appdir/AdGuardHome &amp;amp;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Shutdown script
#!/bin/sh
killall AdGuardHome&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The startup script is doing a few things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Make sure that the data directory has the right permissions.&lt;/li&gt;
&lt;li&gt;Initialize configuration if needed (More on that later).&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;The default configuration will listen on port 3000/TCP.  Connect to
it with a web browser to configure &lt;a href=&quot;https://adguard.com/en/adguard-home/overview.html&quot;&gt;AdGuard Home&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/adguardhome/adguard-home-screen-alt.png&quot; alt=&quot;icon&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;Configuration&quot; name=&quot;Configuration&quot;&gt;Configuration&lt;/h2&gt;
&lt;p&gt;Actually, for the initial configuration I prefer to use prepared file.  This is
the &lt;code&gt;AdGuardHome.proto&lt;/code&gt; file that is referenced in the &lt;code&gt;start&lt;/code&gt; script.&lt;/p&gt;
&lt;p&gt;This YAML file can contain the settings that you want to override from the defaults.&lt;/p&gt;
&lt;p&gt;I am using this file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;http:
  address: 10.0.2.254:3000
users:
- name: admin
  password: $2a$....DELETED....
dns:
  bind_hosts:
    - 0.0.0.0
  port: 53
  upstream_dns:
    - &#039;# https://dns10.quad9.net/dns-query&#039;
    - 127.0.0.1:5353
  hostsfile_enabled: false
  use_private_ptr_resolvers: true
  local_ptr_upstreams:
    - 127.0.0.1:5353
log:
  enabled: true
  file: &quot;/var/log/AdGuardHome.log&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;http&lt;/code&gt; section is fixing the IP address and port it will listen to.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;users&lt;/code&gt; section can be used to configure authentication.  You can have multiple
user names and passwords.  The passwords are based on Bcryp2 hashing.  You can use:
&lt;a href=&quot;https://www.devglan.com/online-tools/bcrypt-hash-generator&quot;&gt;https://www.devglan.com/online-tools/bcrypt-hash-generator&lt;/a&gt; to generate the hashes.&lt;/p&gt;
&lt;p&gt;If you do not want to do user authentication, for example, if placing behind a reverse
proxy that does the authentication already, you can use:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;users: []&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This disables the internal user name and password authentication.&lt;/p&gt;
&lt;p&gt;In the &lt;code&gt;dns&lt;/code&gt; section, I am doing a number of tweaks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;upstream dns&lt;/code&gt; and &lt;code&gt;local_ptr_upstream&lt;/code&gt; are configured to point to &lt;code&gt;127.0.0.1:5353&lt;/code&gt;
which is the locally running &lt;a href=&quot;https://thekelleys.org.uk/dnsmasq/doc.html&quot;&gt;dnsmasq&lt;/a&gt; that was re-configured earlier.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hostfile_enabled&lt;/code&gt; is set to &lt;code&gt;false&lt;/code&gt; to disable &lt;code&gt;/etc/hosts&lt;/code&gt; file usage.  Personally
I expect the host names defined by &lt;code&gt;/etc/hosts&lt;/code&gt; &lt;em&gt;only&lt;/em&gt; to be used by the local system,
and shouldn&#039;t be exported to DNS.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Reverse+Proxy&quot; name=&quot;Reverse+Proxy&quot;&gt;Reverse Proxy&lt;/h2&gt;
&lt;p&gt;Reverse proxying can be used to provide for example single sign on (SSO) by handling
the authentication on the reverse proxy itself.  This is very simple to do.  It can
be done either at the virtual host level, or at a sub folder level.  In nginx you can use
the code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;location /adghome/ {
    proxy_pass http://10.0.2.254:3000/;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or using a virtual host:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;location / {
    proxy_pass http://10.0.2.254:3000/;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Using+it&quot; name=&quot;Using+it&quot;&gt;Using it&lt;/h2&gt;
&lt;p&gt;Once properly configured, clients will receive via DHCP the &lt;a href=&quot;https://adguard.com/en/adguard-home/overview.html&quot;&gt;AdGuard Home&lt;/a&gt; and
start using directly.  You can browse to the user interface and you will see
statistics and configuration.&lt;/p&gt;
&lt;h2 id=&quot;Conclusion&quot; name=&quot;Conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In short, &lt;a href=&quot;https://adguard.com/en/adguard-home/overview.html&quot;&gt;AdGuard Home&lt;/a&gt; is much like your run-of-the-mill browser ad blocker, but rather than
being a plug-in on your favourite web browser, &lt;a href=&quot;https://adguard.com/en/adguard-home/overview.html&quot;&gt;AdGuard Home&lt;/a&gt; is a fully-fledged server
application which runs on a separate machine somewhere on your network (or perhaps even on a
VPS you own). Its primary goal is to provide your network with a mechanism to actively block
certain requests that websites you visit make – in this case, requests for adverts, malware,
or various other malicious things.&lt;/p&gt;</content>
</entry>
<entry>
<title>Using SSLH on Alpine Linux</title>
<link href="https://www.0ink.net/posts/2025/2025-09-01-sslh-on-alpine.html"></link>
<id>urn:uuid:34249ca1-6625-2005-4ccc-228b2fd8e4ab</id>
<updated>2025-01-21T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Install sslh
apk add sslh
Configure sslh startup:
# /etc/conf.d/sslh
# configuration for /etc/init.d/sslh
...]]></summary>
<content type="html">&lt;p&gt;&lt;img src=&quot;/images/2025/sslh-icon.png&quot; alt=&quot;icon&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Install sslh&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;apk add sslh&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Configure sslh startup:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# /etc/conf.d/sslh
# configuration for /etc/init.d/sslh
#
# The sslh binary to run; one of:
#
# fork    Forks a new process for each incoming connection. It is well-tested
#         and very reliable, but incurs the overhead of many processes.
# select  Uses only one thread, which monitors all connections at once. It is
#         more recent and less tested, but has smaller overhead per connection.
#mode=&quot;fork&quot;
mode=&quot;select&quot;
# Path of the configuration file.
#cfgfile=&quot;/etc/sslh.conf&quot;
# Additional options to pass to the sslh daemon. See sslh(1) man page.
#command_args=&quot;&quot;
# Uncomment to run the sslh daemon under process supervisor.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We set mode to &lt;code&gt;select&lt;/code&gt; as this is most efficient.  For package versions before
&lt;code&gt;2.1.2-r1&lt;/code&gt; the default &lt;strong&gt;init&lt;/strong&gt; script will fail to start when &lt;code&gt;fork&lt;/code&gt; mode is
selected.&lt;/p&gt;
&lt;p&gt;Create sslh configuration.  For more configuration examples see &lt;a href=&quot;https://github.com/yrutschle/sslh/blob/master/example.cfg&quot;&gt;example.cfg&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# /etc/sslh.conf

timeout: 2;
# Timeout before forwarding the connection to the timeout protocol
# The default is 2s.  If using OpenVPN, this needs to be increased to
# 5s for OpenVPN to work reliably.

transparent: true;
# Make SSLH act as a transparent proxy.  See later for more
# configuration steps.

user: &quot;nobody&quot;;
# Run process as the given user.  Note that transparent mode
# will not work if user is not root.

numeric: true;
# Do not attempt to resolve hostnames: logs will contain IP addresses.
# This is mostly useful if the system&#039;s DNS is slow and running the sslh-select variant,
# as DNS requests will hang all connections.

listen:
(
  { host: &quot;0.0.0.0&quot;; port: &quot;443&quot;; },
  { host: &quot;::&quot;; port: &quot;443&quot;; }
);
# List of listening address and ports.  This is probably enough, but sslh
# can also listen on UDP sockets, IPv6 addresses, and UNIX sockets.

protocols:
(
  { name: &quot;ssh&quot;; host: &quot;localhost&quot;; port: &quot;22&quot;;
    fork: true;},
  { name: &quot;tls&quot;; host: &quot;localhost&quot;; port: &quot;8443&quot;;
    sni_hostnames: [ &quot;app1.example.com&quot;, &quot;app2.example.com&quot; ];
    log_level: 0;  tfo_ok: true },
  { name: &quot;tls&quot;; host: &quot;localhost&quot;; port: &quot;9443&quot;;
    sni_hostnames: [ &quot;app3.example.com&quot;, &quot;app4.example.com&quot; ];
    log_level: 0;  tfo_ok: true },
  { name: &quot;tls&quot;; host: &quot;localhost&quot;; port: &quot;7443&quot;;
    log_level: 0; tfo_ok: true; },
  { name: &quot;tls&quot;; host: &quot;localhost&quot;; port: &quot;443&quot;;
    log_level: 0; tfo_ok: true; },  
);
# Connections that match the given protocol will be forwarded to host:port.
# For TLS, you can match based on sni_hostnames from the TLS handshake.
# Options:
# log_level: 0 turn off logging
#            1 logs every incoming request.  Hor https, makes sense to turn off logging
# fork: For sslh-select, will fork a new process for the connection
# tfo_ok: Enable TCP fast open.

# on-timeout: &quot;ssh&quot;;
# By default it will connect to the &quot;ssh&quot; specification on time-out.  If
# you want to change the time out target, create a &quot;timeout&quot; protocol
# and set on-timeout: to &quot;timeout&quot;.  The default to connect to &quot;ssh&quot;
# on timeout is a sane default.

# Logging configuration
# Value: 1: stdout; 2: syslog; 3: stdout+syslog; 4: logfile; ...; 7: all
verbose-config: 0; #  print configuration at startup
verbose-config-error: 3;  # print configuration errors
verbose-connections: 3; # trace established incoming address to forward address
verbose-connections-error: 3; # connection errors
verbose-connections-try: 3; # connection attempts towards targets
verbose-fd: 0; # file descriptor activity, open/close/whatnot
verbose-packets: 0; # hexdump packets on which probing is done
verbose-probe-info: 3; # what&#039;s happening during the probe process
verbose-probe-error: 3; # failures and problems during probing
verbose-system-error: 3; # system call problem, i.e.  malloc, fork, failing
verbose-int-error: 3; # internal errors, the kind that should never happen```&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For https traffic, it is advisable to enable &lt;a href=&quot;https://en.wikipedia.org/wiki/TCP_Fast_Open&quot;&gt;tfo_ok&lt;/a&gt; as it enables
&lt;a href=&quot;https://en.wikipedia.org/wiki/TCP_Fast_Open&quot;&gt;TCP Fast Open&lt;/a&gt; feature, which reduces the connection time by reducing
round-trips.&lt;/p&gt;
&lt;p&gt;For trasnparent proxying, I have tested two scenarios.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;One host running &lt;a href=&quot;https://www.rutschle.net/tech/sslh/README.html&quot;&gt;sslh&lt;/a&gt; &lt;em&gt;and&lt;/em&gt; sshd and web server.
&lt;div&gt;&lt;svg class=&quot;bob&quot; font-family=&quot;arial&quot; font-size=&quot;14&quot; height=&quot;96&quot; width=&quot;400&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;defs&gt;
&lt;marker id=&quot;triangle&quot; markerHeight=&quot;10&quot; markerUnits=&quot;strokeWidth&quot; markerWidth=&quot;10&quot; orient=&quot;auto&quot; refX=&quot;15&quot; refY=&quot;10&quot; viewBox=&quot;0 0 50 20&quot;&gt;
&lt;path d=&quot;M 0 0 L 30 10 L 0 20 z&quot;/&gt;
&lt;/marker&gt;
&lt;/defs&gt;
&lt;style&gt;

    line, path {
      stroke: black;
      stroke-width: 2;
      stroke-opacity: 1;
      fill-opacity: 1;
      stroke-linecap: round;
      stroke-linejoin: miter;
    }
    circle {
      stroke: black;
      stroke-width: 2;
      stroke-opacity: 1;
      fill-opacity: 1;
      stroke-linecap: round;
      stroke-linejoin: miter;
    }
    circle.solid {
      fill:black;
    }
    circle.open {
      fill:transparent;
    }
    tspan.head{
        fill: none;
        stroke: none;
    }
    
&lt;/style&gt;
&lt;path d=&quot; M 176 8 A 4 4 0 0 0 172 12 M 176 8 L 184 8 M 176 8 L 184 8 M 188 12 A 4 4 0 0 0 184 8 M 320 8 A 4 4 0 0 0 316 12 M 320 8 L 328 8 M 320 8 L 328 8 L 336 8 M 328 8 L 336 8 L 344 8 M 336 8 L 344 8 L 352 8 M 344 8 L 352 8 L 360 8 M 352 8 L 360 8 L 368 8 M 360 8 L 368 8 L 376 8 M 368 8 L 376 8 L 384 8 M 376 8 L 384 8 M 388 12 A 4 4 0 0 0 384 8 M 152 24 A 4 4 0 0 0 148 28 M 152 24 L 160 24 M 152 24 L 160 24 M 164 24 L 160 24 M 196 16 L 196 32 M 196 16 L 196 32 M 308 16 L 308 32 M 308 16 L 308 32 M 396 16 L 396 32 M 396 16 L 396 32 M 4 40 L 8 40 M 4 40 L 4 48 M 8 40 L 16 40 M 8 40 L 16 40 L 24 40 M 16 40 L 24 40 L 32 40 M 24 40 L 32 40 L 40 40 M 32 40 L 40 40 L 48 40 M 40 40 L 48 40 L 56 40 M 48 40 L 56 40 L 64 40 M 56 40 L 64 40 L 72 40 M 64 40 L 72 40 M 76 40 L 72 40 M 76 40 L 76 48 M 120 40 A 4 4 0 0 0 116 44 L 116 48 M 120 40 L 128 40 M 120 40 L 128 40 L 136 40 M 128 40 L 136 40 M 140 40 L 136 40 M 204 36 A 4 4 0 0 0 208 40 L 216 40 M 208 40 L 216 40 L 224 40 M 216 40 L 224 40 M 228 44 A 4 4 0 0 0 224 40 M 308 32 L 308 48 M 308 32 L 308 48 M 320 40 L 328 40 M 320 40 L 328 40 M 396 32 L 396 48 M 396 32 L 396 48 M 4 48 L 4 64 M 4 48 L 4 64 M 76 48 L 76 64 M 76 48 L 76 64 M 88 56 L 96 56 M 88 56 L 96 56 M 116 48 L 116 64 M 116 48 L 116 64 M 236 48 L 236 64 M 236 48 L 236 64 M 248 56 L 256 56 M 248 56 L 256 56 L 264 56 M 256 56 L 264 56 L 272 56 M 264 56 L 272 56 L 280 56 M 272 56 L 280 56 L 288 56 M 280 56 L 288 56 M 308 48 L 308 64 M 308 48 L 308 64 M 320 56 L 328 56 M 320 56 L 328 56 M 396 48 L 396 64 M 396 48 L 396 64 M 4 72 L 4 64 M 4 72 L 8 72 L 16 72 M 8 72 L 16 72 L 24 72 M 16 72 L 24 72 L 32 72 M 24 72 L 32 72 L 40 72 M 32 72 L 40 72 L 48 72 M 40 72 L 48 72 L 56 72 M 48 72 L 56 72 L 64 72 M 56 72 L 64 72 L 72 72 M 64 72 L 72 72 M 76 72 L 76 64 M 76 72 L 72 72 M 116 68 L 116 64 M 116 68 A 4 4 0 0 0 120 72 L 128 72 M 120 72 L 128 72 L 136 72 M 128 72 L 136 72 L 144 72 M 136 72 L 144 72 L 152 72 M 144 72 L 152 72 L 160 72 M 152 72 L 160 72 L 168 72 M 160 72 L 168 72 L 176 72 M 168 72 L 176 72 L 184 72 M 176 72 L 184 72 L 192 72 M 184 72 L 192 72 L 200 72 M 192 72 L 200 72 L 208 72 M 200 72 L 208 72 L 216 72 M 208 72 L 216 72 L 224 72 M 216 72 L 224 72 A 4 4 0 0 0 228 68 M 308 64 L 308 80 M 308 64 L 308 80 M 320 72 L 328 72 M 320 72 L 328 72 M 396 64 L 396 80 M 396 64 L 396 80 M 316 84 A 4 4 0 0 0 320 88 L 328 88 M 320 88 L 328 88 L 336 88 M 328 88 L 336 88 L 344 88 M 336 88 L 344 88 L 352 88 M 344 88 L 352 88 L 360 88 M 352 88 L 360 88 L 368 88 M 360 88 L 368 88 L 376 88 M 368 88 L 376 88 L 384 88 M 376 88 L 384 88 A 4 4 0 0 0 388 84&quot; fill=&quot;none&quot;/&gt;
&lt;path d=&quot;&quot; fill=&quot;none&quot; stroke-dasharray=&quot;3 3&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;88&quot; x2=&quot;84&quot; y1=&quot;56&quot; y2=&quot;56&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;96&quot; x2=&quot;108&quot; y1=&quot;56&quot; y2=&quot;56&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;96&quot; x2=&quot;108&quot; y1=&quot;56&quot; y2=&quot;56&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;104&quot; x2=&quot;108&quot; y1=&quot;56&quot; y2=&quot;56&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;248&quot; x2=&quot;244&quot; y1=&quot;56&quot; y2=&quot;56&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;288&quot; x2=&quot;300&quot; y1=&quot;56&quot; y2=&quot;56&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;288&quot; x2=&quot;300&quot; y1=&quot;56&quot; y2=&quot;56&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;296&quot; x2=&quot;300&quot; y1=&quot;56&quot; y2=&quot;56&quot;/&gt;
&lt;text x=&quot;321&quot; y=&quot;28&quot;&gt;
Server
&lt;/text&gt;
&lt;text x=&quot;337&quot; y=&quot;44&quot;&gt;
sslh
&lt;/text&gt;
&lt;text x=&quot;17&quot; y=&quot;60&quot;&gt;
client
&lt;/text&gt;
&lt;text x=&quot;145&quot; y=&quot;60&quot;&gt;
Internet
&lt;/text&gt;
&lt;text x=&quot;337&quot; y=&quot;60&quot;&gt;
ssh
&lt;/text&gt;
&lt;text x=&quot;337&quot; y=&quot;76&quot;&gt;
nginx
&lt;/text&gt;
&lt;/svg&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;Two hosts, host A running &lt;a href=&quot;https://www.rutschle.net/tech/sslh/README.html&quot;&gt;sslh&lt;/a&gt;, host B running sshd and web server.
Default route of host B points to host A.
&lt;div&gt;&lt;svg class=&quot;bob&quot; font-family=&quot;arial&quot; font-size=&quot;14&quot; height=&quot;96&quot; width=&quot;528&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;defs&gt;
&lt;marker id=&quot;triangle&quot; markerHeight=&quot;10&quot; markerUnits=&quot;strokeWidth&quot; markerWidth=&quot;10&quot; orient=&quot;auto&quot; refX=&quot;15&quot; refY=&quot;10&quot; viewBox=&quot;0 0 50 20&quot;&gt;
&lt;path d=&quot;M 0 0 L 30 10 L 0 20 z&quot;/&gt;
&lt;/marker&gt;
&lt;/defs&gt;
&lt;style&gt;

    line, path {
      stroke: black;
      stroke-width: 2;
      stroke-opacity: 1;
      fill-opacity: 1;
      stroke-linecap: round;
      stroke-linejoin: miter;
    }
    circle {
      stroke: black;
      stroke-width: 2;
      stroke-opacity: 1;
      fill-opacity: 1;
      stroke-linecap: round;
      stroke-linejoin: miter;
    }
    circle.solid {
      fill:black;
    }
    circle.open {
      fill:transparent;
    }
    tspan.head{
        fill: none;
        stroke: none;
    }
    
&lt;/style&gt;
&lt;path d=&quot; M 176 8 A 4 4 0 0 0 172 12 M 176 8 L 184 8 M 176 8 L 184 8 M 188 12 A 4 4 0 0 0 184 8 M 320 8 A 4 4 0 0 0 316 12 M 320 8 L 328 8 M 320 8 L 328 8 L 336 8 M 328 8 L 336 8 L 344 8 M 336 8 L 344 8 L 352 8 M 344 8 L 352 8 L 360 8 M 352 8 L 360 8 L 368 8 M 360 8 L 368 8 L 376 8 M 368 8 L 376 8 M 380 12 A 4 4 0 0 0 376 8 M 456 8 A 4 4 0 0 0 452 12 M 456 8 L 464 8 M 456 8 L 464 8 L 472 8 M 464 8 L 472 8 L 480 8 M 472 8 L 480 8 L 488 8 M 480 8 L 488 8 L 496 8 M 488 8 L 496 8 L 504 8 M 496 8 L 504 8 L 512 8 M 504 8 L 512 8 M 516 12 A 4 4 0 0 0 512 8 M 152 24 A 4 4 0 0 0 148 28 M 152 24 L 160 24 M 152 24 L 160 24 M 164 24 L 160 24 M 196 16 L 196 32 M 196 16 L 196 32 M 308 16 L 308 32 M 308 16 L 308 32 M 388 16 L 388 32 M 388 16 L 388 32 M 444 16 L 444 32 M 444 16 L 444 32 M 524 16 L 524 32 M 524 16 L 524 32 M 4 40 L 8 40 M 4 40 L 4 48 M 8 40 L 16 40 M 8 40 L 16 40 L 24 40 M 16 40 L 24 40 L 32 40 M 24 40 L 32 40 L 40 40 M 32 40 L 40 40 L 48 40 M 40 40 L 48 40 L 56 40 M 48 40 L 56 40 L 64 40 M 56 40 L 64 40 L 72 40 M 64 40 L 72 40 M 76 40 L 72 40 M 76 40 L 76 48 M 120 40 A 4 4 0 0 0 116 44 L 116 48 M 120 40 L 128 40 M 120 40 L 128 40 L 136 40 M 128 40 L 136 40 M 140 40 L 136 40 M 204 36 A 4 4 0 0 0 208 40 L 216 40 M 208 40 L 216 40 L 224 40 M 216 40 L 224 40 M 228 44 A 4 4 0 0 0 224 40 M 308 32 L 308 48 M 308 32 L 308 48 M 388 32 L 388 48 M 388 32 L 388 48 M 444 32 L 444 48 M 444 32 L 444 48 M 524 32 L 524 48 M 524 32 L 524 48 M 4 48 L 4 64 M 4 48 L 4 64 M 76 48 L 76 64 M 76 48 L 76 64 M 88 56 L 96 56 M 88 56 L 96 56 M 116 48 L 116 64 M 116 48 L 116 64 M 236 48 L 236 64 M 236 48 L 236 64 M 248 56 L 256 56 M 248 56 L 256 56 L 264 56 M 256 56 L 264 56 L 272 56 M 264 56 L 272 56 L 280 56 M 272 56 L 280 56 L 288 56 M 280 56 L 288 56 M 308 48 L 308 64 M 308 48 L 308 64 M 320 56 L 328 56 M 320 56 L 328 56 M 388 48 L 388 64 M 388 48 L 388 64 M 400 56 L 408 56 M 400 56 L 408 56 L 416 56 M 408 56 L 416 56 L 424 56 M 416 56 L 424 56 M 444 48 L 444 64 M 444 48 L 444 64 M 456 56 L 464 56 M 456 56 L 464 56 M 524 48 L 524 64 M 524 48 L 524 64 M 4 72 L 4 64 M 4 72 L 8 72 L 16 72 M 8 72 L 16 72 L 24 72 M 16 72 L 24 72 L 32 72 M 24 72 L 32 72 L 40 72 M 32 72 L 40 72 L 48 72 M 40 72 L 48 72 L 56 72 M 48 72 L 56 72 L 64 72 M 56 72 L 64 72 L 72 72 M 64 72 L 72 72 M 76 72 L 76 64 M 76 72 L 72 72 M 116 68 L 116 64 M 116 68 A 4 4 0 0 0 120 72 L 128 72 M 120 72 L 128 72 L 136 72 M 128 72 L 136 72 L 144 72 M 136 72 L 144 72 L 152 72 M 144 72 L 152 72 L 160 72 M 152 72 L 160 72 L 168 72 M 160 72 L 168 72 L 176 72 M 168 72 L 176 72 L 184 72 M 176 72 L 184 72 L 192 72 M 184 72 L 192 72 L 200 72 M 192 72 L 200 72 L 208 72 M 200 72 L 208 72 L 216 72 M 208 72 L 216 72 L 224 72 M 216 72 L 224 72 A 4 4 0 0 0 228 68 M 308 64 L 308 80 M 308 64 L 308 80 M 388 64 L 388 80 M 388 64 L 388 80 M 444 64 L 444 80 M 444 64 L 444 80 M 456 72 L 464 72 M 456 72 L 464 72 M 524 64 L 524 80 M 524 64 L 524 80 M 316 84 A 4 4 0 0 0 320 88 L 328 88 M 320 88 L 328 88 L 336 88 M 328 88 L 336 88 L 344 88 M 336 88 L 344 88 L 352 88 M 344 88 L 352 88 L 360 88 M 352 88 L 360 88 L 368 88 M 360 88 L 368 88 L 376 88 M 368 88 L 376 88 A 4 4 0 0 0 380 84 M 452 84 A 4 4 0 0 0 456 88 L 464 88 M 456 88 L 464 88 L 472 88 M 464 88 L 472 88 L 480 88 M 472 88 L 480 88 L 488 88 M 480 88 L 488 88 L 496 88 M 488 88 L 496 88 L 504 88 M 496 88 L 504 88 L 512 88 M 504 88 L 512 88 A 4 4 0 0 0 516 84&quot; fill=&quot;none&quot;/&gt;
&lt;path d=&quot;&quot; fill=&quot;none&quot; stroke-dasharray=&quot;3 3&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;88&quot; x2=&quot;84&quot; y1=&quot;56&quot; y2=&quot;56&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;96&quot; x2=&quot;108&quot; y1=&quot;56&quot; y2=&quot;56&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;96&quot; x2=&quot;108&quot; y1=&quot;56&quot; y2=&quot;56&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;104&quot; x2=&quot;108&quot; y1=&quot;56&quot; y2=&quot;56&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;248&quot; x2=&quot;244&quot; y1=&quot;56&quot; y2=&quot;56&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;288&quot; x2=&quot;300&quot; y1=&quot;56&quot; y2=&quot;56&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;288&quot; x2=&quot;300&quot; y1=&quot;56&quot; y2=&quot;56&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;296&quot; x2=&quot;300&quot; y1=&quot;56&quot; y2=&quot;56&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;400&quot; x2=&quot;396&quot; y1=&quot;56&quot; y2=&quot;56&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;424&quot; x2=&quot;436&quot; y1=&quot;56&quot; y2=&quot;56&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;424&quot; x2=&quot;436&quot; y1=&quot;56&quot; y2=&quot;56&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;432&quot; x2=&quot;436&quot; y1=&quot;56&quot; y2=&quot;56&quot;/&gt;
&lt;text x=&quot;321&quot; y=&quot;44&quot;&gt;
Host
&lt;/text&gt;
&lt;text x=&quot;361&quot; y=&quot;44&quot;&gt;
A
&lt;/text&gt;
&lt;text x=&quot;457&quot; y=&quot;44&quot;&gt;
Host
&lt;/text&gt;
&lt;text x=&quot;497&quot; y=&quot;44&quot;&gt;
B
&lt;/text&gt;
&lt;text x=&quot;17&quot; y=&quot;60&quot;&gt;
client
&lt;/text&gt;
&lt;text x=&quot;145&quot; y=&quot;60&quot;&gt;
Internet
&lt;/text&gt;
&lt;text x=&quot;337&quot; y=&quot;60&quot;&gt;
sslh
&lt;/text&gt;
&lt;text x=&quot;473&quot; y=&quot;60&quot;&gt;
ssh
&lt;/text&gt;
&lt;text x=&quot;473&quot; y=&quot;76&quot;&gt;
nginx
&lt;/text&gt;
&lt;/svg&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;These an additional scenario are described in the &lt;a href=&quot;https://github.com/yrutschle/sslh/blob/master/doc/scenarios-for-simple-transparent-proxy.md&quot;&gt;sslh documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;Single+host+transparent+proxy&quot; name=&quot;Single+host+transparent+proxy&quot;&gt;Single host transparent proxy&lt;/h2&gt;
&lt;p&gt;In this example, IP address &lt;code&gt;203.0.113.23&lt;/code&gt; is being used.  This is for a a host
with a single external IP interface &lt;code&gt;eth0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Modify OpenSSh configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;# Restrict interfaces that sshd will bind to
ListenAddress 0.0.0.0:22            # Direct access
ListenAddress 203.0.113.23:2201     # Access via sslh&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Modify nginx configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;listen 203.0.113.23:4433 ssl http2;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By using dedicated ports for &lt;a href=&quot;https://www.rutschle.net/tech/sslh/README.html&quot;&gt;sslh&lt;/a&gt; traffic, we make it simpler
to identify the transparently proxied traffic.&lt;/p&gt;
&lt;p&gt;Create a netfilter table ( &lt;code&gt;/etc/nftables.d/sslh.nft&lt;/code&gt; ):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;
#!/usr/sbin/nft -f

# Use ip as we want to configure sslh only for IPV4
table ip sslh {
        chain output {
                type route  hook output  priority -150;
                oif eth0  tcp sport { 2201, 4433 }  counter  mark set 0x4155;
        }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since we were using dedicated ports earlier, we can use these simple netfilter
rules to identify the traffic that is being proxied. We then mark with &lt;code&gt;0x4155&lt;/code&gt;
so that it can be directed to the loopback interface.&lt;/p&gt;
&lt;p&gt;In the &lt;code&gt;/etc/network/interfaces&lt;/code&gt; configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;iface eth0 inet static
  address 203.0.113.23
  netmask 255.255.255.0
  up   ip rule add fwmark 0x4155 lookup 100
  down ip rule del fwmark 0x4155 lookup 100
  up   ip route add local 0.0.0.0/0 dev lo table 100
  down ip route del local 0.0.0.0/0 dev lo table 100
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This grabs the traffic marked with &lt;code&gt;0x4155&lt;/code&gt; by the netfilter rules and routes
them to the loopback so &lt;a href=&quot;https://www.rutschle.net/tech/sslh/README.html&quot;&gt;sslh&lt;/a&gt; can pick it up.&lt;/p&gt;
&lt;p&gt;See &lt;a href=&quot;https://wiki.meurisse.org/wiki/sslh&quot;&gt;sslh installation&lt;/a&gt; on how to install on a debian system.&lt;/p&gt;
&lt;h2 id=&quot;Two+host+transparent+proxy&quot; name=&quot;Two+host+transparent+proxy&quot;&gt;Two host transparent proxy&lt;/h2&gt;
&lt;p&gt;I find this configuration simpler/nicer as does not use nftables rules.
You need to add the following routing rules somewhere.  I am using a file in
&lt;code&gt;/etc/local.d&lt;/code&gt; directory for &lt;a href=&quot;https://alpinelinux.org/&quot;&gt;Alpine Linux&lt;/a&gt;.
Other options are the startscript of sslh, the &lt;code&gt;/etc/conf.d/sslh&lt;/code&gt; file, the
configuration of the outgoing interface.  The lines needed are:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ip rule add from $HOST_B_ADDRESS/32 table 100
ip route add local 0.0.0.0/0 dev lo table 100&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you are forwarding connections to more hosts, you need to add those rules here.&lt;/p&gt;
&lt;p&gt;It is important to note that the &lt;code&gt;$HOST_B_ADDRESS&lt;/code&gt; mentioned needs to be an IP
address dedicated to &lt;a href=&quot;https://www.rutschle.net/tech/sslh/README.html&quot;&gt;sslh&lt;/a&gt; traffic.  Otherwise normal traffic for host
B would also be redirected to the host A loopback device.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/sslh-mux.png&quot; alt=&quot;mux&quot; /&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>Writting a Geany plugin</title>
<link href="https://www.0ink.net/posts/2025/2025-08-15-macrec.html"></link>
<id>urn:uuid:251eec3c-28dd-91ae-c80d-cafe5d3c8f7e</id>
<updated>2025-01-15T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Pre-requisites
Registering
Building
Binding keys
Callbacks
...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Pre-requisites&quot;&gt;Pre-requisites&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Registering&quot;&gt;Registering&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Building&quot;&gt;Building&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Binding+keys&quot;&gt;Binding keys&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Callbacks&quot;&gt;Callbacks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Convenience+functions&quot;&gt;Convenience functions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Recording&quot;&gt;Recording&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Playback&quot;&gt;Playback&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Finishing+touches&quot;&gt;Finishing touches&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/geany-logo.png&quot; alt=&quot;geany logo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I wrote a simple C language plugin for &lt;a href=&quot;https://www.geany.org/ge&quot;&gt;Geany&lt;/a&gt;.  It is similar to geany&#039;s standard
&lt;a href=&quot;https://plugins.geany.org/keyrecord.html&quot;&gt;keyrecord&lt;/a&gt; plugin which simply records and playbacks key sequences.  However unlike
&lt;a href=&quot;https://plugins.geany.org/keyrecord.html&quot;&gt;keyrecord&lt;/a&gt; which uses &lt;code&gt;key events&lt;/code&gt;, it uses the &lt;a href=&quot;https://scintilla.org/ScintillaDoc.html&quot;&gt;Scintilla&lt;/a&gt; macro recording
functionality like &lt;a href=&quot;https://plugins.geany.org/geanymacro.html&quot;&gt;geanymacros&lt;/a&gt; plugin.  However, unlike &lt;a href=&quot;https://plugins.geany.org/geanymacro.html&quot;&gt;geanymacros&lt;/a&gt; does
not do macro editing, and binding macros to multiple keys.&lt;/p&gt;
&lt;p&gt;My plugin can be found in this Github &lt;a href=&quot;https://github.com/TortugaLabs/geany-macro-recorder-plugin&quot;&gt;repo&lt;/a&gt;.  Most of the boiler plate I
got it from another plugin, &lt;a href=&quot;https://github.com/leifmariposa/geany-ctrl-tab-plugin&quot;&gt;Ctrl-Alt&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;Pre-requisites&quot; name=&quot;Pre-requisites&quot;&gt;Pre-requisites&lt;/h2&gt;
&lt;p&gt;I use &lt;a href=&quot;https://voidlinux.org/&quot;&gt;void-linux&lt;/a&gt;, so in &lt;a href=&quot;https://voidlinux.org/&quot;&gt;void&lt;/a&gt; you first need to install the pre-requisites.
This can be done with the command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo xbps-install -S base-devel geany-devel&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Registering&quot; name=&quot;Registering&quot;&gt;Registering&lt;/h2&gt;
&lt;p&gt;Firs you need to include &lt;code&gt;&amp;lt;geanyplugin.h&amp;gt;&lt;/code&gt; which brings all the [Geany][geany] API as well as
the necessary GTK headers.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;lt;geanyplugin.h&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and export a function named &lt;code&gt;geany_load_module()&lt;/code&gt;.
This function is used to tell &lt;a href=&quot;https://www.geany.org/ge&quot;&gt;Geany&lt;/a&gt; of your plugin.  This includes metadata
such as name, description, version, etc.  But also includes pointers to function such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;init&lt;/code&gt; - plugin initialization&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cleanup&lt;/code&gt; - finalization&lt;/li&gt;
&lt;li&gt;&lt;code&gt;configure&lt;/code&gt; - optional configuration function&lt;/li&gt;
&lt;li&gt;&lt;code&gt;help&lt;/code&gt; - optional help functionality&lt;/li&gt;
&lt;li&gt;&lt;code&gt;callbacks&lt;/code&gt; - optional array of &lt;code&gt;PluginCallback&lt;/code&gt; functions&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Building&quot; name=&quot;Building&quot;&gt;Building&lt;/h2&gt;
&lt;p&gt;To compile a plugin:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gcc -c plugin.c -fPIC `pkg-config --cflags geany` &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Linking the plugin:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gcc plugin.o -o plugin.so -shared `pkg-config --libs geany` &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If all went OK, put the library into one of the paths Geany looks for plugins, e.g.
&lt;code&gt;$prefix/lib/geany&lt;/code&gt; or &lt;code&gt;$HOME/.config/geany/plugins&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;Binding+keys&quot; name=&quot;Binding+keys&quot;&gt;Binding keys&lt;/h2&gt;
&lt;p&gt;Previous versions of the &lt;a href=&quot;https://www.geany.org/ge&quot;&gt;Geany&lt;/a&gt; API a plugin would register bindings itself.
Currently, a plugin defines a binding as a _group&lt;em&gt;name&lt;/em&gt; and _key&lt;em&gt;name&lt;/em&gt; and
the user can configure the actual key combination.&lt;/p&gt;
&lt;p&gt;To define the key bindings you must add to the init function:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;key_group = plugin_set_key_group(plugin, PLUGIN_KEY_NAME, KB_COUNT, NULL);
keybindings_set_item(key_group, KB_RECORD, macrec_record, 0, 0,
            &quot;record&quot;, &quot;Start/Stop recording macro&quot;, NULL);
keybindings_set_item(key_group, KB_PLAY, macrec_play, 0, 0,
            &quot;playback&quot;, &quot;Playback macro&quot;, NULL);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;PLUGIN_KEY_NAME&lt;/code&gt; is a string for naming the key group.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KB_COUNT&lt;/code&gt; is the number of keys in the key group.  This is defined
using an &lt;em&gt;C&lt;/em&gt; &lt;code&gt;enum&lt;/code&gt;.  &lt;code&gt;KB_RECORD&lt;/code&gt; and &lt;code&gt;KB_PLAY&lt;/code&gt; are in the same &lt;code&gt;enum&lt;/code&gt;
and are unique numbers identifying a key combination.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;macrec_record&lt;/code&gt; and &lt;code&gt;macrec_play&lt;/code&gt; are the callback functions executed
when the given keybinding is pressed.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Callbacks&quot; name=&quot;Callbacks&quot;&gt;Callbacks&lt;/h2&gt;
&lt;p&gt;Callbacks are functions called whenever a &lt;a href=&quot;https://www.geany.org/ge&quot;&gt;Geany&lt;/a&gt; event is fired.  These
are defined in &lt;code&gt;geany_load_module&lt;/code&gt; function as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;plugin-&amp;gt;funcs-&amp;gt;callbacks = macrec_callbacks;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;macrec_callbacks&lt;/code&gt; is defined as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;static PluginCallback macrec_callbacks[] =
{
        { &quot;editor-notify&quot;, (GCallback) &amp;amp;on_editor_notify, FALSE, NULL },
        { &quot;document-close&quot;, (GCallback) &amp;amp;on_document_close, FALSE, NULL },
        { NULL, NULL, FALSE, NULL }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We are hooking to events, &lt;code&gt;editor-notify&lt;/code&gt; which is used for recording macros
and uses the &lt;a href=&quot;https://scintilla.org/ScintillaDoc.html&quot;&gt;Scintilla&lt;/a&gt; built-in recording facility.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;document-close&lt;/code&gt; is used to abort recording of macros.&lt;/p&gt;
&lt;h2 id=&quot;Convenience+functions&quot; name=&quot;Convenience+functions&quot;&gt;Convenience functions&lt;/h2&gt;
&lt;p&gt;For this plugin I am using a few convenience functions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.geany.org/manual/reference/ui__utils_8h.html#aa0948006d2f45a2a2a6b7da40169b8ac&quot;&gt;ui_set_statusbar&lt;/a&gt; - Display text on the status bar.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.geany.org/manual/reference/msgwindow_8h.html#a90a1a349312c1aece2f85773ee8d6ec1&quot;&gt;msgwin_status_add&lt;/a&gt; - Logs a formatted status message &lt;em&gt;without&lt;/em&gt;
setting the status bar.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.geany.org/manual/reference/utils_8h.html#a03db6196a66b7e3a93784bc40a9e599a&quot;&gt;utils_open_browser&lt;/a&gt; - Tries to open the given URI in a browser.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Recording&quot; name=&quot;Recording&quot;&gt;Recording&lt;/h2&gt;
&lt;p&gt;For macro recording we using &lt;a href=&quot;https://scintilla.org/ScintillaDoc.html&quot;&gt;Scintilla&lt;/a&gt; functionality.  This is in
contrast to the &lt;a href=&quot;https://plugins.geany.org/keyrecord.html&quot;&gt;keyrecord&lt;/a&gt; plugin which uses low-level GTK functionality
which for me is not vedry reliable.&lt;/p&gt;
&lt;p&gt;Start recording:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;scintilla_send_message(document_get_current()-&amp;gt;editor-&amp;gt;sci,SCI_STARTRECORD,0,0);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After that, we capture events using &lt;code&gt;editor-notify&lt;/code&gt; callback.  In that function
we use a table to classify and identify the messages we want to handle:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;/* structure to hold details of Macro for macro editor */
typedef struct {
  gint message;
  const gchar *description;
} MacroDetailEntry;

/* list of editor messages this plugin can handle &amp;amp; a description */
const MacroDetailEntry MacroDetails[]={
{SCI_CUT,N_(&quot;Cut to Clipboard&quot;)},
{SCI_COPY,N_(&quot;Copy to Clipboard&quot;)},
{SCI_PASTE,N_(&quot;Paste from Clipboard&quot;)},
{SCI_LINECUT,N_(&quot;Cut current line to Clipboard&quot;)},
{SCI_LINECOPY,N_(&quot;Copy current line to Clipboard&quot;)},&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;... content deleted ...&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;{0,NULL}
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once recording we stop &lt;a href=&quot;https://scintilla.org/ScintillaDoc.html&quot;&gt;Scintilla&lt;/a&gt; macro recording.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;scintilla_send_message(document_get_current()-&amp;gt;editor-&amp;gt;sci,SCI_STOPRECORD,0,0);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/SciTEIco.png&quot; alt=&quot;sci logo&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;Playback&quot; name=&quot;Playback&quot;&gt;Playback&lt;/h2&gt;
&lt;p&gt;Since the recording is done using &lt;a href=&quot;https://scintilla.org/ScintillaDoc.html&quot;&gt;Scintilla&lt;/a&gt; facilities we
have to use &lt;a href=&quot;https://scintilla.org/ScintillaDoc.html&quot;&gt;Scintilla&lt;/a&gt; playback.  This is realized with this
function call:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;scintilla_send_message(sci,me-&amp;gt;message,me-&amp;gt;wparam,(sptr_t)clipboardcontents);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To make sure that the undo history is consistent, we call:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;scintilla_send_message(sci,SCI_BEGINUNDOACTION,0,0);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;before playback, and call:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;scintilla_send_message(sci,SCI_ENDUNDOACTION,0,0);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to finish the &lt;strong&gt;UNDO&lt;/strong&gt; action.&lt;/p&gt;
&lt;h2 id=&quot;Finishing+touches&quot; name=&quot;Finishing+touches&quot;&gt;Finishing touches&lt;/h2&gt;
&lt;p&gt;I also added a github action to the &lt;a href=&quot;https://github.com/TortugaLabs/geany-macro-recorder-plugin&quot;&gt;repository&lt;/a&gt; to compile the
code (to ensure that things at least compile).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/gh-actions.png&quot; alt=&quot;gh actions logo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;For that I am using a simple C/C++ workflow.  What I added was the
task to make sure that the correct dependancies are available:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: Install dependancies
  run: sudo apt-get install -y libgtk-3-dev autoconf automake autopoint gettext geany
&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>Cinnamon Keyboard shortcuts</title>
<link href="https://www.0ink.net/posts/2025/2025-08-01-cinnamon-shortcuts.html"></link>
<id>urn:uuid:6db924e6-7750-a695-b090-43f0c9e4e9ad</id>
<updated>2025-01-21T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
To familiarize with the Cinnamon desktop, here is a list of its shortcuts:



Function
...]]></summary>
<content type="html">&lt;p&gt;&lt;img src=&quot;/images/2025/kbd-settings.png&quot; alt=&quot;Settings&quot; /&gt;&lt;/p&gt;
&lt;p&gt;To familiarize with the &lt;a href=&quot;https://github.com/linuxmint/cinnamon&quot;&gt;Cinnamon&lt;/a&gt; desktop, here is a list of its shortcuts:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Keys&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Show the window selection screen&lt;/td&gt;
&lt;td&gt;General&lt;/td&gt;
&lt;td&gt;Ctrl+Alt+Down&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Show the workspace selection screen&lt;/td&gt;
&lt;td&gt;General&lt;/td&gt;
&lt;td&gt;Ctrl+Alt+Up, Alt+F1, Super+Tab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Show desktop&lt;/td&gt;
&lt;td&gt;General&lt;/td&gt;
&lt;td&gt;Super+D&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Show Desklets&lt;/td&gt;
&lt;td&gt;General&lt;/td&gt;
&lt;td&gt;Super+S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cycle through open windows&lt;/td&gt;
&lt;td&gt;General&lt;/td&gt;
&lt;td&gt;Alt+Tab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cycle backwards through open windows&lt;/td&gt;
&lt;td&gt;General&lt;/td&gt;
&lt;td&gt;Shift+Alt+Tab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cycle through windows from all workspaces&lt;/td&gt;
&lt;td&gt;General&lt;/td&gt;
&lt;td&gt;Ctrl+Alt+Tab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cycle backwards through windows from all workspaces&lt;/td&gt;
&lt;td&gt;General&lt;/td&gt;
&lt;td&gt;Ctrl+Shift+Alt+Tab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Run dialog&lt;/td&gt;
&lt;td&gt;General&lt;/td&gt;
&lt;td&gt;Alt+F2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Toggle Looking Glass&lt;/td&gt;
&lt;td&gt;General:Troubleshooting&lt;/td&gt;
&lt;td&gt;Ctrl+Alt+L &lt;em&gt;(Default: Super+L)&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unmaximize window&lt;/td&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;td&gt;Alt+F5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Close window&lt;/td&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;td&gt;Alt+F4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Activate window menu&lt;/td&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;td&gt;Alt+Space&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Toggle maximization state&lt;/td&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;td&gt;Alt+F10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resize window&lt;/td&gt;
&lt;td&gt;Windows:Positioning&lt;/td&gt;
&lt;td&gt;Alt+F8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move window&lt;/td&gt;
&lt;td&gt;Windows:Positioning&lt;/td&gt;
&lt;td&gt;Alt+F7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Push tile left&lt;/td&gt;
&lt;td&gt;Windows:Tiling and Snapping&lt;/td&gt;
&lt;td&gt;Super+Left&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Push tile right&lt;/td&gt;
&lt;td&gt;Windows:Tiling and Snapping&lt;/td&gt;
&lt;td&gt;Super+Right&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Push tile up&lt;/td&gt;
&lt;td&gt;Windows:Tiling and Snapping&lt;/td&gt;
&lt;td&gt;Super+Up&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Push tile down&lt;/td&gt;
&lt;td&gt;Windows:Tiling and Snapping&lt;/td&gt;
&lt;td&gt;Super+Down&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move window to left workspace&lt;/td&gt;
&lt;td&gt;Windows:Inter-workspace&lt;/td&gt;
&lt;td&gt;Shift+Ctrl+Alt+Left&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move window to right workspace&lt;/td&gt;
&lt;td&gt;Windows:Inter-workspace&lt;/td&gt;
&lt;td&gt;Shift+Ctrl+Alt+Right&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move window to workspace above&lt;/td&gt;
&lt;td&gt;Windows:Inter-workspace&lt;/td&gt;
&lt;td&gt;Shift+Ctrl+Alt+Up&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move window to workspace below&lt;/td&gt;
&lt;td&gt;Windows:Inter-workspace&lt;/td&gt;
&lt;td&gt;Shift+Ctrl+Alt+Down&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move window to left monitor&lt;/td&gt;
&lt;td&gt;Windows:Inter-monitor&lt;/td&gt;
&lt;td&gt;Shift+Super+Left&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move window to right monitor&lt;/td&gt;
&lt;td&gt;Windows:Inter-monitor&lt;/td&gt;
&lt;td&gt;Shift+Super+Right&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move window to monitor above&lt;/td&gt;
&lt;td&gt;Windows:Inter-monitor&lt;/td&gt;
&lt;td&gt;Shift+Super+Up&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move window to monitor below&lt;/td&gt;
&lt;td&gt;Windows:Inter-monitor&lt;/td&gt;
&lt;td&gt;Shift+Super+Down&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Switch to left workspace&lt;/td&gt;
&lt;td&gt;Workspaces&lt;/td&gt;
&lt;td&gt;Ctrl+Alt+Left&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Switch to right workspace&lt;/td&gt;
&lt;td&gt;Workspaces&lt;/td&gt;
&lt;td&gt;Ctrl+Alt+Right&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Log out&lt;/td&gt;
&lt;td&gt;System&lt;/td&gt;
&lt;td&gt;Ctrl+Alt+Delete&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shut down&lt;/td&gt;
&lt;td&gt;System&lt;/td&gt;
&lt;td&gt;Ctrl+Alt+End&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lock screen&lt;/td&gt;
&lt;td&gt;System&lt;/td&gt;
&lt;td&gt;Super+L, &lt;kbd&gt;Screensaver&lt;/kbd&gt; &lt;em&gt;(Default: Ctrl+Alt+L)&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Suspend&lt;/td&gt;
&lt;td&gt;System&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;Sleep&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hibernate&lt;/td&gt;
&lt;td&gt;System&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;Suspend&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Restart Cinnamon&lt;/td&gt;
&lt;td&gt;System&lt;/td&gt;
&lt;td&gt;Ctrl+Alt+Escape&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Switch monitor configurations&lt;/td&gt;
&lt;td&gt;System:Hardware&lt;/td&gt;
&lt;td&gt;Super+P, &lt;kbd&gt;Display&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rotate display&lt;/td&gt;
&lt;td&gt;System:Hardware&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;RotateWindows&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Orientation Lock&lt;/td&gt;
&lt;td&gt;System:Hardware&lt;/td&gt;
&lt;td&gt;Super+O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Increase screen brightness&lt;/td&gt;
&lt;td&gt;System:Hardware&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;MonitorBrignessUp&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decrease screen brightness&lt;/td&gt;
&lt;td&gt;System:Hardware&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;MonitorBrignessDown&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Toggle keyboard backlight&lt;/td&gt;
&lt;td&gt;System:Hardware&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;KbdLightOnOff&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Increase keyboard backlight level&lt;/td&gt;
&lt;td&gt;System:Hardware&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;KbdLightUp&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decrease keyboard backlight level&lt;/td&gt;
&lt;td&gt;System:Hardware&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;KbdLightDown&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Toggle touchpad state&lt;/td&gt;
&lt;td&gt;System:Hardware&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;TouchpadToggle&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Turn touchpad on&lt;/td&gt;
&lt;td&gt;System:Hardware&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;TouchpadOn&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Turn touchpad off&lt;/td&gt;
&lt;td&gt;System:Hardware&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;TouchpadOff&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Show power statistics&lt;/td&gt;
&lt;td&gt;System:Hardware&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;Battery&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Take a screenshot of an area&lt;/td&gt;
&lt;td&gt;System:Screenshots and Recording&lt;/td&gt;
&lt;td&gt;Shift+Print&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Copy a screenshot of an area to clipboard&lt;/td&gt;
&lt;td&gt;System:Screenshots and Recording&lt;/td&gt;
&lt;td&gt;Ctrl+Shift+Print&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Take a screenshot&lt;/td&gt;
&lt;td&gt;System:Screenshots and Recording&lt;/td&gt;
&lt;td&gt;Unassigned, &lt;em&gt;Default: Print&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Copy a screenshot to clipboard&lt;/td&gt;
&lt;td&gt;System:Screenshots and Recording&lt;/td&gt;
&lt;td&gt;Ctrl+Print&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Take a screenshot of a window&lt;/td&gt;
&lt;td&gt;System:Screenshots and Recording&lt;/td&gt;
&lt;td&gt;Alt+Print&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Copy a screenshot of a window to clipboard&lt;/td&gt;
&lt;td&gt;System:Screenshots and Recording&lt;/td&gt;
&lt;td&gt;Ctrl+Alt+Print&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Toggle recording desktop&lt;/td&gt;
&lt;td&gt;System:Screenshots and Recording&lt;/td&gt;
&lt;td&gt;Shift+Ctrl+Alt+R&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Launch terminal&lt;/td&gt;
&lt;td&gt;Launchers&lt;/td&gt;
&lt;td&gt;Ctrl+Alt+T&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Launch calculator&lt;/td&gt;
&lt;td&gt;Launchers&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;Calculator&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Launch email client&lt;/td&gt;
&lt;td&gt;Launchers&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;Mail&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Launch web browser&lt;/td&gt;
&lt;td&gt;Launchers&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;WWW&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Home folder&lt;/td&gt;
&lt;td&gt;Launchers&lt;/td&gt;
&lt;td&gt;Super+E, &lt;kbd&gt;Explorer&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Search&lt;/td&gt;
&lt;td&gt;Launchers&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;Search&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Volume mute&lt;/td&gt;
&lt;td&gt;Sound and Media&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;AudioMute&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Volume down&lt;/td&gt;
&lt;td&gt;Sound and Media&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;AudioLowerVolume&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Volume up&lt;/td&gt;
&lt;td&gt;Sound and Media&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;AudioRaiseVolume&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mic mute&lt;/td&gt;
&lt;td&gt;Sound and Media&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;AudioMicMute&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Launch media player&lt;/td&gt;
&lt;td&gt;Sound and Media&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;AudioMedia&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Play&lt;/td&gt;
&lt;td&gt;Sound and Media&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;AudioPlay&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pause playback&lt;/td&gt;
&lt;td&gt;Sound and Media&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;AudioPause&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stop playback&lt;/td&gt;
&lt;td&gt;Sound and Media&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;AudioStop&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Previous track&lt;/td&gt;
&lt;td&gt;Sound and Media&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;AudioPrev&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Next track&lt;/td&gt;
&lt;td&gt;Sound and Media&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;AudioNext&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Eject&lt;/td&gt;
&lt;td&gt;Sound and Media&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;Eject&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rewind&lt;/td&gt;
&lt;td&gt;Sound and Media&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;AudioRewind&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fast-forward&lt;/td&gt;
&lt;td&gt;Sound and Media&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;AudioFastForward&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Volume mute (Quiet)&lt;/td&gt;
&lt;td&gt;Sound and Media:Quiet Keys&lt;/td&gt;
&lt;td&gt;Alt+ &lt;kbd&gt;AudioMute&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Volume down (Quiet)&lt;/td&gt;
&lt;td&gt;Sound and Media:Quiet Keys&lt;/td&gt;
&lt;td&gt;Alt+ &lt;kbd&gt;AudioLowerVolume&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Volume up (Quiet)&lt;/td&gt;
&lt;td&gt;Sound and Media:Quiet Keys&lt;/td&gt;
&lt;td&gt;Alt+ &lt;kbd&gt;AudioRaiseVolume&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zoom in&lt;/td&gt;
&lt;td&gt;Universal Access&lt;/td&gt;
&lt;td&gt;Alt+Super+=&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zoom out&lt;/td&gt;
&lt;td&gt;Universal Access&lt;/td&gt;
&lt;td&gt;Alt+Super+-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Turn screen reader on or off&lt;/td&gt;
&lt;td&gt;Universal Access&lt;/td&gt;
&lt;td&gt;Alt+Super+S&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Spices, these relate to the panel applets:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Keys&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Show Calendar&lt;/td&gt;
&lt;td&gt;Calendar&lt;/td&gt;
&lt;td&gt;Super+C&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Global hotkey or cycling through thumbnail menus&lt;/td&gt;
&lt;td&gt;Grouped window list&lt;/td&gt;
&lt;td&gt;&lt;em&gt;unassigned&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Global hotkey to show the order of apps&lt;/td&gt;
&lt;td&gt;Grouped window list&lt;/td&gt;
&lt;td&gt;&lt;em&gt;unnassigned, Default: Super+`&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keyboard shortcut to open and close the menu&lt;/td&gt;
&lt;td&gt;Menu&lt;/td&gt;
&lt;td&gt;Super&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Show Network Mgr menu&lt;/td&gt;
&lt;td&gt;Network Manager&lt;/td&gt;
&lt;td&gt;Shift+Super+N&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Show notifications&lt;/td&gt;
&lt;td&gt;Notifications&lt;/td&gt;
&lt;td&gt;Super+N&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clear notifications&lt;/td&gt;
&lt;td&gt;Notifications&lt;/td&gt;
&lt;td&gt;Shift+Super+C&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Show Sound menu&lt;/td&gt;
&lt;td&gt;Sound&lt;/td&gt;
&lt;td&gt;Shift+Super+S&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Unasigned functions&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Move pointer to the next monitor&lt;/td&gt;
&lt;td&gt;General:Pointer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move pointer to the previous monitor&lt;/td&gt;
&lt;td&gt;General:Pointer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maximize window&lt;/td&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Miminize window&lt;/td&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Raise window&lt;/td&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lower window&lt;/td&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Toggle fullscreen state&lt;/td&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Toggle shaded state&lt;/td&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Toggle always on top&lt;/td&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Toggle showing window on all workspaces&lt;/td&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Increase opacity&lt;/td&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decrease opacity&lt;/td&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Toggle vertical maximization&lt;/td&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Toggle horizontal maximization&lt;/td&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Center window in screen&lt;/td&gt;
&lt;td&gt;Windows:Positioning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move window to upper-right&lt;/td&gt;
&lt;td&gt;Windows:Positioning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move window to upper-left&lt;/td&gt;
&lt;td&gt;Windows:Positioning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move window to lower-right&lt;/td&gt;
&lt;td&gt;Windows:Positioning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move window to lower-left&lt;/td&gt;
&lt;td&gt;Windows:Positioning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move window to right edge&lt;/td&gt;
&lt;td&gt;Windows:Positioning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move window to top edge&lt;/td&gt;
&lt;td&gt;Windows:Positioning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move window to bottom edge&lt;/td&gt;
&lt;td&gt;Windows:Positioning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move window to left edge&lt;/td&gt;
&lt;td&gt;Windows:Positioning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move window to new workspace&lt;/td&gt;
&lt;td&gt;Windows:Inter-workspace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move window to workspace 1-12&lt;/td&gt;
&lt;td&gt;Windows:Inter-workspace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Switch to workspace 1-12&lt;/td&gt;
&lt;td&gt;Workspaces:Direct Navigation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Launch help browser&lt;/td&gt;
&lt;td&gt;Launchers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Repeat&lt;/td&gt;
&lt;td&gt;Sound and Media&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shuffle&lt;/td&gt;
&lt;td&gt;Sound and Media&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Turn on-screen keyboard on or off&lt;/td&gt;
&lt;td&gt;Universal Access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Increase text size&lt;/td&gt;
&lt;td&gt;Universal Access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decrease text size&lt;/td&gt;
&lt;td&gt;Universal Access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High contrast on or off&lt;/td&gt;
&lt;td&gt;Universal Access&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;My custom commands:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Keys&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;System Monitor&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gnome-system-monitor -r&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Ctrl+Alt+S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Switch Workspace&lt;/td&gt;
&lt;td&gt;&lt;code&gt;wsswitch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Super+`&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Find Dialog&lt;/td&gt;
&lt;td&gt;&lt;code&gt;catfish&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Super+F&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Run Dialog&lt;/td&gt;
&lt;td&gt;&lt;code&gt;rundlg&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Super+R&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Screenshot UI&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gnome-screenshot -i&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;Print&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Show windows in All Workspaces&lt;/td&gt;
&lt;td&gt;&lt;code&gt;rofi -show window&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Ctrl+Escape&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;patoggle&lt;/td&gt;
&lt;td&gt;&lt;code&gt;patoggle output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Super+KP_Insert&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;</content>
</entry>
<entry>
<title>Lookup Ansible vars from the command line</title>
<link href="https://www.0ink.net/posts/2025/2025-07-25-ansible-vars.html"></link>
<id>urn:uuid:e6e50d96-2e9a-124f-f5f1-b5ca7e5af6d9</id>
<updated>2025-09-15T00:00:00+02:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[This is a quickie.  If you want to look-up the value of an Ansible variable from
the command line:

ansible localhost -m ansible.builtin.debug -a "var=my_variable"
...]]></summary>
<content type="html">&lt;p&gt;This is a &lt;strong&gt;quickie&lt;/strong&gt;.  If you want to look-up the value of an Ansible variable from
the command line:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;
ansible localhost -m ansible.builtin.debug -a &quot;var=my_variable&quot;&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>Booting Refind on KVM</title>
<link href="https://www.0ink.net/posts/2025/2025-07-20-refind.html"></link>
<id>urn:uuid:aa4d5837-f837-cca3-01a0-15c24fc48d45</id>
<updated>2025-07-15T00:00:00+02:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
This is a quick recipe to boot a refind image on KVM.  This is just to test UEFI boot
configuration in virtualized environments.
First step is to create a refind boot image.  For that I use this script:

To use this script, you need to download the binary zip file refind image from here.
...]]></summary>
<content type="html">&lt;p&gt;&lt;img src=&quot;/images/2025/lnxboot/refind-sm.png&quot; alt=&quot;refind-pic&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This is a quick recipe to boot a &lt;a href=&quot;https://www.rodsbooks.com/refind/&quot;&gt;refind&lt;/a&gt; image on KVM.  This is just to test UEFI boot
configuration in virtualized environments.&lt;/p&gt;
&lt;p&gt;First step is to create a &lt;a href=&quot;https://www.rodsbooks.com/refind/&quot;&gt;refind&lt;/a&gt; boot image.  For that I use this script:&lt;/p&gt;
&lt;script src=&quot;https://tortugalabs.github.io/embed-like-gist/embed.js?style=github&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showCopy=on&amp;amp;fetchFromJsDelivr=off&amp;amp;target=https://github.com/alejandroliu/0ink.net/blob/main/snippets/2025/mkimg.sh&quot;&gt;&lt;/script&gt;
&lt;p&gt;To use this script, you need to download the binary zip file refind image from &lt;a href=&quot;https://www.rodsbooks.com/refind/getting.html&quot;&gt;here&lt;/a&gt;.
Also, the following packages need to be installed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;util-linux&lt;/code&gt; : for &lt;code&gt;sfdisk&lt;/code&gt; and &lt;code&gt;fallocate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;unzip&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dosfstools&lt;/code&gt; : for &lt;code&gt;mkfs.vfat&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mtools&lt;/code&gt; : for &lt;code&gt;mcopy&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And create the image using the command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sh mkimg.sh refind-bin-0.14.2.zip boot.img 12G&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To use the newly created image in a KVM environment.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/uefi_forum.png&quot; alt=&quot;uefi logo&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;virt-install \
        --name vm1 \
        --memory 1024 \
        --vcpus 1 \
        --clock offset=utc \
        --disk boot.img,format=raw \
        --osinfo linux2022 \
        --graphics vnc \
        --autoconsole text \
        --virt-type kvm \
        --console pty \
        --rng /dev/urandom} \
        --import \
        --boot uefi&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;memory is in Megabytes.&lt;/li&gt;
&lt;li&gt;osinfo values can be found using the command &lt;code&gt;virt-install --osinfo list&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The main settings to pay attention here is &lt;code&gt;--boot uefi&lt;/code&gt;, which tells the system
to set up a UEFI VM.&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Saving Cinnamon customizations</title>
<link href="https://www.0ink.net/posts/2025/2025-07-15-customizing-cinnamon.html"></link>
<id>urn:uuid:56c4618e-c539-6138-b26b-bcef359d5d03</id>
<updated>2025-01-03T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
When I switched Desktop Environments towards Cinnamon, I wanted
to save configuration settings so I could restore them later if I moved
to a different PC.
Pre-requisites:
We need the following packages
...]]></summary>
<content type="html">&lt;p&gt;&lt;img src=&quot;/images/2025/cinn-settings.png&quot; alt=&quot;Settings&quot; /&gt;&lt;/p&gt;
&lt;p&gt;When I switched Desktop Environments towards &lt;a href=&quot;https://github.com/linuxmint/cinnamon&quot;&gt;Cinnamon&lt;/a&gt;, I wanted
to save configuration settings so I could restore them later if I moved
to a different PC.&lt;/p&gt;
&lt;p&gt;Pre-requisites:&lt;/p&gt;
&lt;p&gt;We need the following packages&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;dconf&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Optionally you may want to install:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;dconf-editor&lt;/li&gt;
&lt;li&gt;glib&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The simplest way is to modify the settings using the built-in &lt;a href=&quot;https://github.com/linuxmint/cinnamon&quot;&gt;Cinnamon&lt;/a&gt;
UI tools or &lt;code&gt;cinnamon-settings&lt;/code&gt;.  Once you have all the settings as you
want run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;dconf dump / &amp;gt; customizations.ini&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or if you want to limit to cinnamon settings:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;dconf dump /org/cinnamon/  &amp;gt; cinnamon.ini
dconf dump /org/gnome/ &amp;gt; gnome.ini
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We need to dump &lt;code&gt;cinnamon&lt;/code&gt; and &lt;code&gt;gnome&lt;/code&gt; because &lt;a href=&quot;https://github.com/linuxmint/cinnamon&quot;&gt;cinnamon&lt;/a&gt; is gnome derived
and some settings are stored there.&lt;/p&gt;
&lt;p&gt;See the &lt;a href=&quot;https://man.archlinux.org/man/dconf.1.en&quot;&gt;manpage for dconf&lt;/a&gt; for more details.&lt;/p&gt;
&lt;p&gt;The result of these commands is a text file in &lt;a href=&quot;https://en.wikipedia.org/wiki/INI_file&quot;&gt;INI&lt;/a&gt; format.  Which can be
edited with a text editor.  I would normally edit the text file as it would
contain some settings that I would prefer to leave as default.&lt;/p&gt;
&lt;p&gt;When it is time to restore the settings you need to use the command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;dconf load / &amp;lt; customizations.ini&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;dconf load /org/cinnamon/  &amp;lt; cinnamon.ini
dconf load /org/gnome/ &amp;lt; gnome.ini&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Modifying the &lt;a href=&quot;https://en.wikipedia.org/wiki/INI_file&quot;&gt;INI&lt;/a&gt; file lets you access some settings not available
directly from the GUI.  To explore what is possible you can use the
following commands:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gsettings list-schemas&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or from the GUI:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;dconf-editor&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/dconf.png&quot; alt=&quot;dconf&quot; /&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>Linux booting Linux</title>
<link href="https://www.0ink.net/posts/2025/2025-07-01-lnxboot.html"></link>
<id>urn:uuid:ebe5433f-8aac-182a-6aa4-02a3a05a3e83</id>
<updated>2025-01-01T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Introduction
Backing up
Installing Refind
Custom Linux

...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Introduction&quot;&gt;Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Backing+up&quot;&gt;Backing up&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Installing+Refind&quot;&gt;Installing Refind&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Custom+Linux&quot;&gt;Custom Linux&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#voidlinux+pre-requisites&quot;&gt;voidlinux pre-requisites&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Setting+up+Buildroot&quot;&gt;Setting up Buildroot&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/lnxboot/refind-sm.png&quot; alt=&quot;boot screen&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;Introduction&quot; name=&quot;Introduction&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;This is an amusing weekend project.  I have an old &lt;a href=&quot;https://support.hp.com/us-en/product/setup-user-guides/hp-stream-11-pro-g5-notebook-pc/21949282&quot;&gt;HP Stream 11 Pro G5&lt;/a&gt;
laptop that I wanted to repurpose.  It originally came pre-installed with Windows 10,
and I wanted to use it with Linux.  Since my Linux distro of choice is &lt;a href=&quot;https://voidlinux.org/&quot;&gt;voidlinux&lt;/a&gt;
I downloaded the Live Image and wrote it to a USB drive.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/lnxboot/hpstream.png&quot; alt=&quot;Laptop&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I plugged the USB drive into the laptop and turned in on.  I immeditely spammed the &lt;code&gt;Escape&lt;/code&gt;
key to get the &lt;em&gt;boot menu&lt;/em&gt;.  After selecting the USB drive as the boot device I booted
into Linux.  I was able to confirm that &lt;a href=&quot;https://voidlinux.org/&quot;&gt;voidlinux&lt;/a&gt; was able to recognize most of the
hardware in the laptop.&lt;/p&gt;
&lt;p&gt;Normally I run my systems by booting from USB drive.  However for this laptop that would pose
a problem becase this laptop is very &lt;strong&gt;basic&lt;/strong&gt; and only has &lt;strong&gt;two&lt;/strong&gt; USB ports (which are USB2
to boot).&lt;/p&gt;
&lt;p&gt;My standard configuration is boot from USB, and the data is in the internal drive.  If later
I want to change Operating Systems I only need to create a new USB drive and plug it in.&lt;/p&gt;
&lt;p&gt;This approach with this &lt;a href=&quot;https://support.hp.com/us-en/product/setup-user-guides/hp-stream-11-pro-g5-notebook-pc/21949282&quot;&gt;HP Stream 11 Pro G5&lt;/a&gt; is not good since as I mention
earlier, there are only &lt;strong&gt;two&lt;/strong&gt; USB2 ports.   The laptop does have a Micro SD card
slot, so instead of creating a USB stick I could simply write to a Micro SD card.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/lnxboot/left.png&quot; alt=&quot;left side&quot; /&gt;
&lt;img src=&quot;/images/2025/lnxboot/right.png&quot; alt=&quot;right side&quot; /&gt;&lt;/p&gt;
&lt;p&gt;So I wrote the live &lt;a href=&quot;https://voidlinux.org/&quot;&gt;voidlinux&lt;/a&gt; image to a Micro SD card and try to boot from
it.  Unfortunately, the UEFI BIOS did not recognize the Micro SD card reader,
much less detect it as a bootable device.  So what I did is use &lt;a href=&quot;https://www.rodsbooks.com/refind/&quot;&gt;rEFInd&lt;/a&gt;
as a boot manager to be able to boot either Windows or Linux.  If Linux it
would boot into a &lt;em&gt;small&lt;/em&gt; Linux image.  This &lt;em&gt;small&lt;/em&gt; Linux image would then
load a Linux kernel and InitRAM image from the Micro SD card and boot from it.&lt;/p&gt;
&lt;p&gt;This makes it possible to boot SD card images just like I do with USB sticks.&lt;/p&gt;
&lt;h2 id=&quot;Backing+up&quot; name=&quot;Backing+up&quot;&gt;Backing up&lt;/h2&gt;
&lt;p&gt;Since I really don&#039;t use MS-Windows much, I decided to remove the Windows system
from laptop.  However, since I am sure in the future I would want to send this
laptop for re-purposing, I would first need to backup the internal MMC contents.&lt;/p&gt;
&lt;p&gt;The internal drive is only 64GB, with compression, it can be backed-up to a 32GB
USB stick.  For this I am using &lt;a href=&quot;https://clonezilla.org/&quot;&gt;clonezilla&lt;/a&gt; Live.  Since this laptop
has UEFI boot, preparing a suitable media is very easy.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/lnxboot/clonezilla_logo.png&quot; alt=&quot;clonezilla logo&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Start with an empty USB stick.&lt;/li&gt;
&lt;li&gt;Create two partitions:
&lt;ul&gt;
&lt;li&gt;1GB VFAT partition : for &lt;a href=&quot;https://clonezilla.org/&quot;&gt;clonezilla&lt;/a&gt; software&lt;/li&gt;
&lt;li&gt;remaining space as an &lt;code&gt;xfs&lt;/code&gt; or &lt;code&gt;ext4&lt;/code&gt; partition to store the backup images&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Create the filesystems on the first and second partitions.&lt;/li&gt;
&lt;li&gt;Download &lt;a href=&quot;https://clonezilla.org/&quot;&gt;clonezilla&lt;/a&gt; Live.  I am using the &lt;strong&gt;stable&lt;/strong&gt; Debian based
version.  If you have more exotic hardware you may need to use the
Ubuntu based version.&lt;/li&gt;
&lt;li&gt;Unzip the contents of &lt;a href=&quot;https://clonezilla.org/&quot;&gt;clonezilla&lt;/a&gt; Live to the first partition of
USB drive.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;With this you have suitable media to perform the backup.  As before
plug the USB stick to the laptop, spam the &lt;code&gt;Escape&lt;/code&gt; key until you
can get the boot menu and select to boot from the USB stick.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;From the GRUB menu, select Clonezilla Live.  Since I am old,
I selected the &lt;code&gt;VGA with Large Font&lt;/code&gt; option.  Becaue this loads
everything to RAM, this would take a bit longer than usual.&lt;/li&gt;
&lt;li&gt;After configuring the region and keyboard type, select &lt;code&gt;Start Clonezilla&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Choose &lt;code&gt;device-image&lt;/code&gt; mode and select &lt;code&gt;local_dev&lt;/code&gt; in the next menu. &lt;br /&gt;Breeze through the next couple of screens as the target drive is already
inserted.&lt;/li&gt;
&lt;li&gt;For the &lt;code&gt;/home/partimag&lt;/code&gt; repository select the second (large) partition
on the USB stick. &lt;br /&gt;The next skip asks for &lt;code&gt;fsck&lt;/code&gt;.  Just do &lt;code&gt;no-fsck&lt;/code&gt; since it is a new
partition. &lt;br /&gt;Then select the root folder of the partition when prompted.&lt;/li&gt;
&lt;li&gt;Select &lt;code&gt;Beginner&lt;/code&gt; when asked for the &lt;strong&gt;Wizard mode&lt;/strong&gt; to use.&lt;/li&gt;
&lt;li&gt;Select &lt;code&gt;savedisk&lt;/code&gt; to backup the whole MMC drive, and give a suitable name
to the image set.&lt;/li&gt;
&lt;li&gt;Select the drive to backup.  In my laptop, it is shown as &lt;code&gt;mmcblk1&lt;/code&gt;. &lt;br /&gt;For compression option I chose &lt;code&gt;z9p&lt;/code&gt; for max compression. &lt;br /&gt;The next options are just for error checking.  Pick what you think its
best.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/lnxboot/running.png&quot; alt=&quot;PC running clonezilla&quot; /&gt;&lt;/p&gt;
&lt;p&gt;That starts the backup process.  After some time the backup process
will finnish.  You can put away the USB drive in a safe place.  You
will need it to reset the laptop for re-use.&lt;/p&gt;
&lt;p&gt;For this step there a several options.  I chose &lt;a href=&quot;https://clonezilla.org/&quot;&gt;clonezilla&lt;/a&gt; because
was the simplest and most straightforward.  Personally, I do not like
the user experience of the Live USB, but it works.&lt;/p&gt;
&lt;p&gt;Underneath it uses &lt;a href=&quot;https://www.partimage.org/&quot;&gt;partimage&lt;/a&gt; which is no
longer an active project.  One could have used instead a standard live
distro and use &lt;a href=&quot;https://www.fsarchiver.org/&quot;&gt;fsarchiver&lt;/a&gt; or
&lt;a href=&quot;https://partclone.org/&quot;&gt;partclone&lt;/a&gt;.  With this we have a simple
purpose build USB that contains the restore images in one single
package.&lt;/p&gt;
&lt;h2 id=&quot;Installing+Refind&quot; name=&quot;Installing+Refind&quot;&gt;Installing Refind&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/lnxboot/refind-logo.png&quot; alt=&quot;refind logo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You can download &lt;a href=&quot;https://www.rodsbooks.com/refind/&quot;&gt;refind&lt;/a&gt; from its download page:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.rodsbooks.com/refind/getting.html&quot;&gt;https://www.rodsbooks.com/refind/getting.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The detailed installation instructions can be found here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.rodsbooks.com/refind/installing.html#linux&quot;&gt;https://www.rodsbooks.com/refind/installing.html#linux&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For my case I used  the &lt;a href=&quot;https://voidlinux.org/&quot;&gt;voidlinux&lt;/a&gt; live
USB I used before for testing.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Boot into &lt;a href=&quot;https://voidlinux.org/&quot;&gt;voidlinux&lt;/a&gt; live USB.&lt;/li&gt;
&lt;li&gt;Since we booted using the USB drive, the EFI partitions is usually
not mounted.  To mount:
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mount -t vfat /dev/mmcblk1p1 /mnt&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Download the binary zip file from &lt;a href=&quot;https://www.rodsbooks.com/refind/getting.html&quot;&gt;getting refind&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Copy refind to the EFI partition:
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cp -av refind-bin-VERSION/refind /mnt/EFI/refind&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Remove unneeded drivers and/or architectures:
&lt;pre&gt;&lt;code&gt;rm -r drivers_ia32 drivers_aa64&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Rename the &lt;code&gt;refind.conf-sample&lt;/code&gt; to &lt;code&gt;refind.conf&lt;/code&gt; and edit it to taste. &lt;br /&gt;For example, make sure that &lt;code&gt;showtools&lt;/code&gt; line contains &lt;code&gt;bootorder&lt;/code&gt; which
I use in a later step.&lt;/li&gt;
&lt;li&gt;Add &lt;a href=&quot;https://www.rodsbooks.com/refind/&quot;&gt;refind&lt;/a&gt; to the list of available boot loadders:
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;efibootmgr -c -l \\EFI\\refind\\refind_x64.efi -L rEFInd -d /dev/mmcblk1&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Install additional components (See &lt;a href=&quot;https://www.rodsbooks.com/refind/installing.html#addons&quot;&gt;addons&lt;/a&gt;.
Myself, I download the &lt;code&gt;shell.efi&lt;/code&gt; file.   &lt;/li&gt;
&lt;li&gt;Re-boot system and spam the &lt;code&gt;Escape&lt;/code&gt; key while booting to get to the BIOS
menu.  Select &lt;code&gt;rEFInd&lt;/code&gt;.  Use the &lt;a href=&quot;https://www.rodsbooks.com/refind/&quot;&gt;refind&lt;/a&gt; utility to change the
boot order to make sure that &lt;a href=&quot;https://www.rodsbooks.com/refind/&quot;&gt;refind&lt;/a&gt; is &lt;strong&gt;before&lt;/strong&gt; the
&lt;code&gt;Windows boot manager&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Up to now, we are able to boot Linux from either the internal MMC or from a USB drive.
In order to boot from the MicroSD card slot we need to create a custom Linux
image that can be used to select and load a kernel from the MicroSD card.  For that
we use &lt;a href=&quot;https://buildroot.org/downloads/manual/manual.html#_buildroot_quick_start&quot;&gt;buildroot&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The purpose of &lt;a href=&quot;https://www.rodsbooks.com/refind/&quot;&gt;refind&lt;/a&gt; here is to easily support dual-booting Linux and
Windows.  It is unfortunate that the EFI run-time does not seem to support the MicroSD
card reader.  If that was the case we could stop now.&lt;/p&gt;
&lt;p&gt;There are multiple alternatives to &lt;a href=&quot;https://www.rodsbooks.com/refind/&quot;&gt;refind&lt;/a&gt; such as &lt;a href=&quot;https://partclone.org/&quot;&gt;GNU Grub2&lt;/a&gt;.
I find grub very Linux oriented and I find the configuration system overly complicated.&lt;/p&gt;
&lt;h2 id=&quot;Custom+Linux&quot; name=&quot;Custom+Linux&quot;&gt;Custom Linux&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://buildroot.org/downloads/manual/manual.html#_buildroot_quick_start&quot;&gt;Buildroot&lt;/a&gt; is a tool that simplifies and automates the process of building a complete Linux
system for an embedded system, using cross-compilation.&lt;/p&gt;
&lt;p&gt;In order to achieve this, &lt;a href=&quot;https://buildroot.org/downloads/manual/manual.html#_buildroot_quick_start&quot;&gt;Buildroot&lt;/a&gt; is able to generate a cross-compilation toolchain, a
root filesystem, a Linux kernel image and a bootloader for your target. &lt;a href=&quot;https://buildroot.org/downloads/manual/manual.html#_buildroot_quick_start&quot;&gt;Buildroot&lt;/a&gt; can be used
for any combination of these options, independently (you can for example use an existing
cross-compilation toolchain, and build only your root filesystem with &lt;a href=&quot;https://buildroot.org/downloads/manual/manual.html#_buildroot_quick_start&quot;&gt;Buildroot&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/lnxboot/br2.png&quot; alt=&quot;br2 logo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;For our use-case we need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Linux kernel with &lt;code&gt;kexec&lt;/code&gt; function call enabled and a number of drivers built-in&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kexec&lt;/code&gt; utilities/userspace&lt;/li&gt;
&lt;li&gt;Some basic user space utilties (mostly provided by &lt;code&gt;busybox&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dialog&lt;/code&gt; utiltiy for user displays&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With &lt;a href=&quot;https://buildroot.org/downloads/manual/manual.html#_buildroot_quick_start&quot;&gt;buildroot&lt;/a&gt; we can create a Linux kernel with the necessary drivers and
a initrd image with enough utilities to read the MicroSD card reader, give the
option to pick a kernel and boot into it.&lt;/p&gt;
&lt;h3 id=&quot;voidlinux+pre-requisites&quot; name=&quot;voidlinux+pre-requisites&quot;&gt;voidlinux pre-requisites&lt;/h3&gt;
&lt;p&gt;As one of the steps in &lt;a href=&quot;https://buildroot.org/downloads/manual/manual.html#_buildroot_quick_start&quot;&gt;buildroot&lt;/a&gt; it will build its own &lt;code&gt;fakeroot&lt;/code&gt;
command.  This does not work in &lt;a href=&quot;https://voidlinux.org/&quot;&gt;voidlinux&lt;/a&gt; with musl runtime.  You
must either switch to &lt;a href=&quot;https://voidlinux.org/&quot;&gt;voidlinux&lt;/a&gt; with glibc or follow my recipe
in &lt;a href=&quot;/posts/2019/2019-04-10-void-musl-with-glibc.html&quot;&gt;Mixing glibc with musl&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Preparing a &lt;a href=&quot;https://voidlinux.org/&quot;&gt;voidlinux&lt;/a&gt; system with musl runtime:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo env XBPS_ARCH=x86_64 xbps-install \
        --repository=https://repo-default.voidlinux.org/current  \
        -r /glibc -S \
        base-voidstrap&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You must also install the following package build dependancies:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;base-devel ncurses-devel rsync wget cpio&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Setting+up+Buildroot&quot; name=&quot;Setting+up+Buildroot&quot;&gt;Setting up Buildroot&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://buildroot.org/downloads/manual/manual.html#_buildroot_quick_start&quot;&gt;buildroot&lt;/a&gt; is distributed via a git repository.  From this
repository you can select the version that you want to use.&lt;/p&gt;
&lt;p&gt;Fetch &lt;a href=&quot;https://buildroot.org/downloads/manual/manual.html#_buildroot_quick_start&quot;&gt;buildroot&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone  https://git.buildroot.net/buildroot
cd buildroot
git co -b dev 2024.02.9&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I have only tested version &lt;code&gt;2024.02.9&lt;/code&gt;.  The &lt;a href=&quot;https://buildroot.org/downloads/manual/manual.html#_buildroot_quick_start&quot;&gt;buildroot&lt;/a&gt; project has a &lt;strong&gt;LTS&lt;/strong&gt;
schema where the &lt;code&gt;YYYY.02&lt;/code&gt; releases are considered for long term support/more stable.&lt;/p&gt;
&lt;p&gt;I am using out-of-tree builds and also customizations are kept separte from &lt;a href=&quot;https://buildroot.org/downloads/manual/manual.html#_buildroot_quick_start&quot;&gt;buildroot&lt;/a&gt;
using the &lt;a href=&quot;https://buildroot.org/downloads/manual/manual.html#outside-br-custom&quot;&gt;BR2_EXTERNAL&lt;/a&gt;
functionality.&lt;/p&gt;
&lt;p&gt;This keeps things nice and tidy.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://buildroot.org/downloads/manual/manual.html#_buildroot_quick_start&quot;&gt;buildroot&lt;/a&gt; customizatins are in separate &lt;a href=&quot;https://github.com/TortugaLabs/lnxboot&quot;&gt;github&lt;/a&gt; repository.  To use
it you need to fetch it with git.&lt;/p&gt;
&lt;p&gt;Fech &lt;a href=&quot;https://github.com/TortugaLabs/lnxboot&quot;&gt;lnxboot&lt;/a&gt; repository:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone https://github.com/TortugaLabs/lnxboot.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For the out-of-tree build, you just need to create an empty directory and
and set-things up.&lt;/p&gt;
&lt;p&gt;Create a folder for out-of-tree building:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir build_dir
cd build_dir
glibc make O=$(pwd) BR2_EXTERNAL=$(readlink -f ../lnxboot) -C ../buildroot lnxboot_defconfig

glibc make&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;The first &lt;code&gt;make&lt;/code&gt; sets up the out-of-tree build environment.  We use &lt;code&gt;defconfig&lt;/code&gt;
target so that the &lt;code&gt;savedefconfig&lt;/code&gt; function works correctly.&lt;/li&gt;
&lt;li&gt;The second &lt;code&gt;make&lt;/code&gt; applies the configuration settings for &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You will find in &lt;code&gt;images&lt;/code&gt; of &lt;code&gt;build_dir&lt;/code&gt; two files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;bzImage&lt;/li&gt;
&lt;li&gt;rootfs.cpio.gz&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Copy this to the EFI partition and update &lt;code&gt;refind.conf&lt;/code&gt; to boot it.&lt;/p&gt;
&lt;p&gt;From the build directory you can customize things with this commands:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;glibc make menuconfig&lt;/code&gt; - Configures &lt;a href=&quot;https://buildroot.org/downloads/manual/manual.html#_buildroot_quick_start&quot;&gt;buildroot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;glibc make linux-menuconfig&lt;/code&gt; - Configures the Linux kernel&lt;/li&gt;
&lt;li&gt;&lt;code&gt;glibc make busybox-menuconfig&lt;/code&gt; - Configures busybox&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Additional make targets:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;glibc make img&lt;/code&gt; - Create a qemu bootable image (for testing)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;glibc make run-qemu&lt;/code&gt; - Create a qemu bootable image and boot it (for testing)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;glibc make lnxboot-update&lt;/code&gt; - Updates the lnxboot configuration files.&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Nemo file explorer custom actions</title>
<link href="https://www.0ink.net/posts/2025/2025-06-20-cinnamon-actions.html"></link>
<id>urn:uuid:3d26b53a-902e-1464-8805-bdae21e0672e</id>
<updated>2025-09-28T00:00:00+02:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Adding a &quot;Copy File Path&quot; Option to Nemo in Linux Cinnamon

Step 1: Create a Nemo Action File
Step 2: Define the Action

...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Adding+a+%26quot%3BCopy+File+Path%26quot%3B+Option+to+Nemo+in+Linux+Cinnamon&quot;&gt;Adding a &amp;quot;Copy File Path&amp;quot; Option to Nemo in Linux Cinnamon&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Step+1%3A+Create+a+Nemo+Action+File&quot;&gt;Step 1: Create a Nemo Action File&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Step+2%3A+Define+the+Action&quot;&gt;Step 2: Define the Action&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Explanation+of+Key+Parameters%3A&quot;&gt;Explanation of Key Parameters:&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Step+3%3A+Install+Required+Dependencies&quot;&gt;Step 3: Install Required Dependencies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Step+4%3A+Restart+Nemo&quot;&gt;Step 4: Restart Nemo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Step+5%3A+Make+It+Available+for+All+Users+%28Optional%29&quot;&gt;Step 5: Make It Available for All Users (Optional)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Variations&quot;&gt;Variations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Other+conditions&quot;&gt;Other conditions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;Adding+a+%26quot%3BCopy+File+Path%26quot%3B+Option+to+Nemo+in+Linux+Cinnamon&quot; name=&quot;Adding+a+%26quot%3BCopy+File+Path%26quot%3B+Option+to+Nemo+in+Linux+Cinnamon&quot;&gt;Adding a &amp;quot;Copy File Path&amp;quot; Option to Nemo in Linux Cinnamon&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;Nemo&lt;/strong&gt; file browser in &lt;strong&gt;Linux Cinnamon&lt;/strong&gt; is highly customizable, allowing users to
tweak its functionality to suit their workflow. One useful feature missing by default is a
&lt;strong&gt;right-click menu option&lt;/strong&gt; to copy a file&#039;s full path to the clipboard—an essential shortcut
for developers, power users, and anyone working with file paths regularly.&lt;/p&gt;
&lt;p&gt;Note that you could simply download the
&lt;a href=&quot;https://cinnamon-spices.linuxmint.com/actions/view/40&quot;&gt;Copy Path to Cliboard&lt;/a&gt;
from &lt;a href=&quot;https://cinnamon-spices.linuxmint.com/&quot;&gt;Cinnamon Spices&lt;/a&gt;, the &lt;strong&gt;official&lt;/strong&gt;
addons repository.&lt;/p&gt;
&lt;p&gt;In this guide, we&#039;ll show you how to add this feature by creating a &lt;strong&gt;Nemo Action&lt;/strong&gt;, both for
individual users and system-wide use manually.&lt;/p&gt;
&lt;h3 id=&quot;Step+1%3A+Create+a+Nemo+Action+File&quot; name=&quot;Step+1%3A+Create+a+Nemo+Action+File&quot;&gt;Step 1: Create a Nemo Action File&lt;/h3&gt;
&lt;p&gt;First, we need to create a &lt;code&gt;.nemo_action&lt;/code&gt; file that tells Nemo how to handle the new menu option.  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Open a terminal and navigate to the &lt;strong&gt;actions&lt;/strong&gt; folder:  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir -p ~/.local/share/nemo/actions
cd ~/.local/share/nemo/actions&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a new file:  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;touch copy_path.nemo_action&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Step+2%3A+Define+the+Action&quot; name=&quot;Step+2%3A+Define+the+Action&quot;&gt;Step 2: Define the Action&lt;/h3&gt;
&lt;p&gt;Edit the &lt;code&gt;copy_path.nemo_action&lt;/code&gt; file in your favorite text editor and add the following content:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;[Nemo Action]
Name=Copy Full Filepath
Name[fr]=Copier chemin complet
Comment=Copies the full path of the selected file
Comment[fr]=Chemin complet %F 
Exec=sh -c &quot;readlink -n -f &#039;%F&#039; | xclip -selection clipboard&quot;
Icon-Name=edit-copy
Selection=notnone
Extensions=any
Separator=,
Dependencies=readlink;xclip;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;Explanation+of+Key+Parameters%3A&quot; name=&quot;Explanation+of+Key+Parameters%3A&quot;&gt;Explanation of Key Parameters:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Name&lt;/strong&gt;: Defines the name appearing in the right-click menu.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Exec&lt;/strong&gt;: Runs a command using &lt;code&gt;readlink&lt;/code&gt; (to get the full path) and &lt;code&gt;xclip&lt;/code&gt; (to copy it to
the clipboard).  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Selection &amp;amp; Extensions&lt;/strong&gt;: Ensures it only applies to files, not directories.  &lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Step+3%3A+Install+Required+Dependencies&quot; name=&quot;Step+3%3A+Install+Required+Dependencies&quot;&gt;Step 3: Install Required Dependencies&lt;/h3&gt;
&lt;p&gt;Ensure &lt;code&gt;xclip&lt;/code&gt; is installed, as it handles clipboard copying:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo apt install xclip&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Step+4%3A+Restart+Nemo&quot; name=&quot;Step+4%3A+Restart+Nemo&quot;&gt;Step 4: Restart Nemo&lt;/h3&gt;
&lt;p&gt;To apply the changes, restart Nemo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nemo --quit &amp;amp;&amp;amp; nemo &amp;amp;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, when you &lt;strong&gt;right-click&lt;/strong&gt; a file, you should see the &lt;strong&gt;&amp;quot;Copy Full Filepath&amp;quot;&lt;/strong&gt; option. Clicking it copies the selected file&#039;s full path to the clipboard!&lt;/p&gt;
&lt;h3 id=&quot;Step+5%3A+Make+It+Available+for+All+Users+%28Optional%29&quot; name=&quot;Step+5%3A+Make+It+Available+for+All+Users+%28Optional%29&quot;&gt;Step 5: Make It Available for All Users (Optional)&lt;/h3&gt;
&lt;p&gt;If you want all users on the system to have access to this feature, place the &lt;code&gt;.nemo_action&lt;/code&gt; file in the system-wide &lt;strong&gt;Nemo actions directory&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo mv ~/.local/share/nemo/actions/copy_path.nemo_action /usr/share/nemo/actions/&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ensure proper permissions:  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo chmod 644 /usr/share/nemo/actions/copy_path.nemo_action
sudo chown root:root /usr/share/nemo/actions/copy_path.nemo_action&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Restart Nemo once again:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nemo --quit &amp;amp;&amp;amp; nemo &amp;amp;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, &lt;strong&gt;all users&lt;/strong&gt; on the system should have the &amp;quot;Copy Full Filepath&amp;quot; option available!&lt;/p&gt;
&lt;h2 id=&quot;Variations&quot; name=&quot;Variations&quot;&gt;Variations&lt;/h2&gt;
&lt;p&gt;It is possible to restrict the custom action to specific conditions.  For example
if I want the action only to be available in a user&#039;s &lt;code&gt;~/custom&lt;/code&gt; directory you can add
to the action defintion file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;Conditions=path =~ ~/custom/&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You could also use &lt;code&gt;Conditions=path matches $HOME/custom/*&lt;/code&gt; to specify the path.&lt;/p&gt;
&lt;h2 id=&quot;Other+conditions&quot; name=&quot;Other+conditions&quot;&gt;Other conditions&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;&quot;Conditions&quot;&lt;/code&gt; field in Nemo actions allows you to specify when an action should be
available. Here are some common options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;mime&lt;/code&gt;&lt;/strong&gt;: Restrict the action to specific file types (e.g., &lt;code&gt;mime=text/plain&lt;/code&gt; for text files).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;name&lt;/code&gt;&lt;/strong&gt;: Match specific filenames (e.g., &lt;code&gt;name=*.txt&lt;/code&gt; for all &lt;code&gt;.txt&lt;/code&gt; files).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;path&lt;/code&gt;&lt;/strong&gt;: Limit the action to certain directories (e.g., &lt;code&gt;path matches $HOME/special/*&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;selection&lt;/code&gt;&lt;/strong&gt;: Define how many files must be selected (e.g., &lt;code&gt;selection=1&lt;/code&gt; for single-file actions).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;exec&lt;/code&gt;&lt;/strong&gt;: Ensure a command exists before showing the action (e.g., &lt;code&gt;exec=command-name&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;extension&lt;/code&gt;&lt;/strong&gt;: Restrict based on file extensions (e.g., &lt;code&gt;extension=png,jpg&lt;/code&gt; for images).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can combine multiple conditions to fine-tune when your action appears.&lt;/p&gt;
&lt;h2 id=&quot;Conclusion&quot; name=&quot;Conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Adding custom &lt;strong&gt;Nemo Actions&lt;/strong&gt; is a powerful way to extend the functionality of your
&lt;strong&gt;Linux Cinnamon&lt;/strong&gt; file manager. With just a few simple steps, you now have a
&lt;strong&gt;quick and efficient way&lt;/strong&gt; to copy file paths—saving time and effort, especially in
development workflows.  &lt;/p&gt;</content>
</entry>
<entry>
<title>Stretch Cluster notes</title>
<link href="https://www.0ink.net/posts/2025/2025-06-15-stretchcluster.html"></link>
<id>urn:uuid:4813c62e-2221-dca5-d290-71b25cf1b07f</id>
<updated>2024-12-12T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Clustering
Stretched Clusters
Calculating latencies
Common storage stretched clusters

...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Clustering&quot;&gt;Clustering&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Stretched+Clusters&quot;&gt;Stretched Clusters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Calculating+latencies&quot;&gt;Calculating latencies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Common+storage+stretched+clusters&quot;&gt;Common storage stretched clusters&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Ceph&quot;&gt;Ceph&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#VMware+VSAN&quot;&gt;VMware VSAN&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#NetApp+MetroCluster&quot;&gt;NetApp MetroCluster&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Conclusions&quot;&gt;Conclusions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;Clustering&quot; name=&quot;Clustering&quot;&gt;Clustering&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/stretch/cluster.png&quot; alt=&quot;Cluster&quot; /&gt;&lt;/p&gt;
&lt;p&gt;At work, very often we need to implement disaster recovery solution.  One of the
methods to achieve this is to implement stretch clusters.&lt;/p&gt;
&lt;p&gt;A computer cluster is a set of computers that work together so that they can be
viewed as a single system. Computer clusters have each node set to perform the same
task, controlled and scheduled by software.&lt;/p&gt;
&lt;p&gt;The components of a cluster are usually connected to each other through fast local
area networks, with each node (computer used as a server) running its own instance
of an operating system. In most circumstances, all of the nodes use the same
hardware and the same operating system.&lt;/p&gt;
&lt;p&gt;Clusters are usually deployed to improve performance and availability over that of
a single computer, while typically being much more cost-effective than single
computers of comparable speed or availability.&lt;/p&gt;
&lt;h2 id=&quot;Stretched+Clusters&quot; name=&quot;Stretched+Clusters&quot;&gt;Stretched Clusters&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/stretch/stretched.png&quot; alt=&quot;Stretched Cluster&quot; /&gt;&lt;/p&gt;
&lt;p&gt;A very special case of clusters is when instead of using a Local Area Network,
we connect them over a Wide Area Network.&lt;/p&gt;
&lt;p&gt;For disaster recovery with low Recovery Point Objective (RPO) requirements, using stretched
clusters to keep data synchronized accross data centers is a very common approach.&lt;/p&gt;
&lt;p&gt;These typically you would have cluster nodes spread across two locations.  With data being
replicated &lt;strong&gt;synchronously&lt;/strong&gt; between them.  Latency over the Wide Area link becomes
quite important as usually, synchronous protocols would require acknowledgement from
cluster node members on the far away location before completing a transaction.&lt;/p&gt;
&lt;p&gt;Another name for Stretched Clusters is &lt;strong&gt;geo-replicated clusters&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;Calculating+latencies&quot; name=&quot;Calculating+latencies&quot;&gt;Calculating latencies&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/stretch/fiber.png&quot; alt=&quot;Glassfiber&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Latency is a term that is used to describe a time delay in a transmission
medium such as a vacuum, air, or a fiber optic waveguide.  In free space,
light travels at 299,792,458 meters per second.  This equates to 299.792 meters
per microsecond (µs) or 3.34µs per kilometer.  In fiber optics, the latency of
the fiber is the time it takes for light to travel a specified distance through
the glass core of the fiber.  Light moving through the fiber optic core will travel
slower than light through a vacuum because of the differences of the refractive
index of light in free space and in the glass.  See &lt;a href=&quot;https://www.m2optics.com/blog/bid/70587/Calculating-Optical-Fiber-Latency&quot;&gt;m2optics&lt;/a&gt; on how
to calculate the latency based on the distance.&lt;/p&gt;
&lt;p&gt;A rule of thumb for quickly calculating latency for every 100 Km add 1 ms
round trip latency.  This based on single mode fiber is using
5 microseconds per kilometer over a straigth line distance between locations.
This provides a high level estimate of predicted latency.  This estimate is
enough to eliminate possible options that one may be considering when
implementing a DR solution.&lt;/p&gt;
&lt;h2 id=&quot;Common+storage+stretched+clusters&quot; name=&quot;Common+storage+stretched+clusters&quot;&gt;Common storage stretched clusters&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/stretch/clusters.png&quot; alt=&quot;Clusters&quot; /&gt;&lt;/p&gt;
&lt;p&gt;These are common solutions for storage replication that implement stretched
clustering:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ceph.io/&quot;&gt;Ceph&lt;/a&gt; - a free and open-source software-defined storage platform that
provides object storage, block storage, and file storage built on a common
distributed cluster foundation. &lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.vmware.com/products/cloud-infrastructure/vsan&quot;&gt;VMware vSAN&lt;/a&gt; - a software-defined storage that is embedded in VMware&#039;s
ESXi hypervisor.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.netapp.com/media/13480-tr4705.pdf&quot;&gt;NetApp Metrocluster&lt;/a&gt; - a feature of NetApp&#039;s ONTAP software.  It
allows to synchronously replicate data between two sites using SyncMirror.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Ceph&quot; name=&quot;Ceph&quot;&gt;Ceph&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/stretch/ceph.png&quot; alt=&quot;ceph&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://ceph.io/&quot;&gt;Ceph&lt;/a&gt; provides distributed operation without a single point of failure
and scalability to the exabyte level. Ceph does not rely on any other
conventional filesystem and directly manages HDDs and SSDs with its own storage
backend BlueStore and can expose a POSIX filesystem.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://ceph.io/&quot;&gt;Ceph&lt;/a&gt; has a &lt;a href=&quot;https://docs.ceph.com/en/reef/rados/operations/stretch-mode/&quot;&gt;stretch mode&lt;/a&gt; that supports geo-replicated clusters.  &lt;/p&gt;
&lt;p&gt;A stretch cluster is a cluster that has servers in geographically separated
data centers, distributed over a WAN. Stretch clusters have LAN-like high-speed
and low-latency connections, but limited links. Stretch clusters have a higher
likelihood of (possibly asymmetric) network splits, and a higher likelihood of
temporary or complete loss of an entire data center (which can represent one-third
to one-half of the total cluster).&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://ceph.io/&quot;&gt;Ceph&lt;/a&gt; is designed with the expectation that all parts of its
network and cluster will be reliable and that failures will be distributed
randomly across the CRUSH map.  Even if a switch goes down and causes the
loss of many OSDs, &lt;a href=&quot;https://ceph.io/&quot;&gt;Ceph&lt;/a&gt; is designed so that the remaining OSDs and
monitors will route around such a loss.&lt;/p&gt;
&lt;p&gt;Sometimes this cannot be relied upon. If you have a &amp;quot;stretched-cluster&amp;quot; deployment in
which much of your cluster is behind a single network component, you might
need to use stretch mode to ensure data integrity.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://ceph.io/&quot;&gt;Ceph&lt;/a&gt; has two standard configurations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Configuration with two data centers (or, in clouds, two availability zones). &lt;br /&gt;In this configuration, each site needs to hold a copy of the data.  A third site
is required as a tiebreaker monitor.  This tiebreaker monitor picks a winner if
the network connection fails and both data centers remain alive.&lt;/li&gt;
&lt;li&gt;Configuration with three data centers (or, in clouds, three availability zones). &lt;br /&gt;Because there is an odd number of sites, there is no tiebreaker monitor required.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.redhat.com/en/documentation/red_hat_openshift_data_foundation/4.11/html/configuring_openshift_data_foundation_disaster_recovery_for_openshift_workloads/metro-dr-solution#requirements-for-enabling-metro-disaster-recovery_mdr&quot;&gt;RedHat in OpenShift implementations&lt;/a&gt; recomments a 10 ms RTT between sites, with
100 ms RTT for the tiebreaker monitor.  This translates to 1,000 Km distance.  The real
limitation would be application itself, as 10 ms latency for disk writes is quite high.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://ceph.io/&quot;&gt;Ceph&lt;/a&gt; also support &lt;a href=&quot;https://docs.ceph.com/en/reef/rbd/rbd-mirroring/&quot;&gt;asynchronous replication&lt;/a&gt; using &lt;code&gt;rbd-mirror&lt;/code&gt;.  This
does not have a latency limitation, with the disadvantage that RPO can &lt;em&gt;not&lt;/em&gt; be zero.&lt;/p&gt;
&lt;h3 id=&quot;VMware+VSAN&quot; name=&quot;VMware+VSAN&quot;&gt;VMware VSAN&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/stretch/vsan.png&quot; alt=&quot;vsan Cluster&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The vSphere and &lt;a href=&quot;https://www.vmware.com/products/cloud-infrastructure/vsan&quot;&gt;vSAN&lt;/a&gt; software runs on industry-standard x86 servers to form a
hyper-converged infrastructure (or HCI). However, network operators need to have
servers from HCL (Hardware Compatibility List) to put one into production.&lt;/p&gt;
&lt;p&gt;Some &lt;a href=&quot;https://www.vmware.com/products/cloud-infrastructure/vsan&quot;&gt;vSAN&lt;/a&gt; features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;native data at rest encryption&lt;/li&gt;
&lt;li&gt;local protection for stretched clusters&lt;/li&gt;
&lt;li&gt;analytics&lt;/li&gt;
&lt;li&gt;optimized solid-state drive performance&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The latency limits for stretched &lt;a href=&quot;https://www.vmware.com/products/cloud-infrastructure/vsan&quot;&gt;vSAN&lt;/a&gt; clusters is documented in the
&lt;a href=&quot;https://docs.vmware.com/en/VMware-vSphere/7.0/vsan-network-design-guide/GUID-F3401655-6EFA-477B-B072-E8F001B50BCC.html&quot;&gt;vSAN network requirements&lt;/a&gt; documentation:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Site Communication&lt;/th&gt;
&lt;th&gt;Bandwidth&lt;/th&gt;
&lt;th&gt;Latency&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Site to Site&lt;/td&gt;
&lt;td&gt;Minimum of 10 Gbps&lt;/td&gt;
&lt;td&gt;Less than 5 ms RTT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Site to Witness&lt;/td&gt;
&lt;td&gt;2 Mbps per 1,000 vSAN components&lt;/td&gt;
&lt;td&gt;&lt;ul&gt;&lt;li&gt;Less than 500 ms RTT for 1 host per site&lt;/li&gt;&lt;li&gt;Less than 200 ms RTT for up to 10 hosts per site&lt;/li&gt;&lt;li&gt;Less than 100 ms RTT forr 11-15 hosts per site&lt;/li&gt;&lt;/ul&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The latency of 5 ms RTT translates to a maximum of 500 Km distance.&lt;/p&gt;
&lt;h3 id=&quot;NetApp+MetroCluster&quot; name=&quot;NetApp+MetroCluster&quot;&gt;NetApp MetroCluster&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/stretch/metrocluster.png&quot; alt=&quot;Metro Cluster&quot; /&gt;&lt;/p&gt;
&lt;p&gt;NetApp MetroCluster configurations combine array-based clustering with
synchronous replication to deliver continuous availability, immediately
duplicating all of your mission-critical data on a transaction-by-transaction
basis. MetroCluster configurations enhance the built-in high availability and
nondisruptive operations of NetApp hardware and ONTAP storage software,
providing an additional layer of protection for the entire storage and
host environment.&lt;/p&gt;
&lt;p&gt;Latency requirements are documented in &lt;a href=&quot;https://docs.netapp.com/us-en/ontap-metrocluster/install-ip/concept-requirements-isls.html&quot;&gt;MetroCluster consierations for ISLs&lt;/a&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The ISL speed must be least 10Gbps.&lt;/li&gt;
&lt;li&gt;ISL can be no further than 300km and the max RTT cannot exceed 3ms –
whichever is reached first.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Conclusions&quot; name=&quot;Conclusions&quot;&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/stretch/safe.png&quot; alt=&quot;safe computing&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In general a 3 to 5 ms RTT is a reasonable figure.  So usually between 300 to
500 Km distances.&lt;/p&gt;
&lt;p&gt;Recap of solutions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ceph.io/&quot;&gt;Ceph&lt;/a&gt; - open source, software define storage solution&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.vmware.com/products/cloud-infrastructure/vsan&quot;&gt;VMware vSAN&lt;/a&gt; - closed source, software defined storage solution&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.netapp.com/media/13480-tr4705.pdf&quot;&gt;NetApp MetroCluster&lt;/a&gt; - Hardware based storage solution&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Rewriting history in git</title>
<link href="https://www.0ink.net/posts/2025/2025-06-10-gitfilter.html"></link>
<id>urn:uuid:cf987a14-39c8-b348-551a-0baaeab9606b</id>
<updated>2025-05-22T00:00:00+02:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Coordinate with your team
The secret is in the last commit and there is nothing else
The secret is in the last commit but there are other changes
The secret is beyond the last commit

...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Coordinate+with+your+team&quot;&gt;Coordinate with your team&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#The+secret+is+in+the+last+commit+and+there+is+nothing+else&quot;&gt;The secret is in the last commit and there is nothing else&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#The+secret+is+in+the+last+commit+but+there+are+other+changes&quot;&gt;The secret is in the last commit but there are other changes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#The+secret+is+beyond+the+last+commit&quot;&gt;The secret is beyond the last commit&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Backup+your+config&quot;&gt;Backup your config&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Excise+an+entire+file&quot;&gt;Excise an entire file&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Edit+a+file+without+removing+it&quot;&gt;Edit a file without removing it&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Restoring+your+config&quot;&gt;Restoring your config&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Publishing+your+repo&quot;&gt;Publishing your repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Coordinate+with+your+team&quot;&gt;Coordinate with your team&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;From &lt;a href=&quot;https://blog.gitguardian.com/rewriting-git-history-cheatsheet/&quot;&gt;gitguardian blog&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You know that &lt;a href=&quot;https://blog.gitguardian.com/secrets-credentials-api-git/&quot;&gt;adding secrets to your git repository&lt;/a&gt; (even a private one) is a
bad idea, because doing so &lt;strong&gt;risks exposing confidential information to the world&lt;/strong&gt;.
But mistakes were made, and now you need to figure out how to excise confidential
information from your repo. &lt;strong&gt;Because git keeps a history of everything, it&#039;s not
often enough to simply remove the secret or file, commit, and push:&lt;/strong&gt; we might need
to do a bit of deep cleaning.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Thankfully, for simpler cases, git provides commands that make cleaning things
up easy.&lt;/strong&gt; And in more complicated cases, we can use &lt;a href=&quot;https://github.com/newren/git-filter-repo&quot;&gt;git-filter-repo&lt;/a&gt;, a tool
recommended by the core git developers for deep cleaning an entire repository.&lt;/p&gt;
&lt;p&gt;First and foremost, if there is reason to think that the secret has escaped into
the world, and you can revoke the secret, do so. How to revoke a secret is going
to vary quite a lot depending on what the secret protects. If you don&#039;t know how
to revoke it, you will need help from the owner of the resource protected by
the secret.&lt;/p&gt;
&lt;h2 id=&quot;Coordinate+with+your+team&quot; name=&quot;Coordinate+with+your+team&quot;&gt;Coordinate with your team&lt;/h2&gt;
&lt;p&gt;If you are working with a team, make sure you coordinate things with the team.&lt;/p&gt;
&lt;p&gt;First of all, we need to determine who else is affected by the secret&#039;s presence,
because we&#039;ll need to coordinate everyone&#039;s actions. If the secret only appears in
the branch you&#039;re working on, you only need to coordinate with anyone else who is
always working off of that branch. However, if you found the secret lurking further
back in git history, perhaps in your &lt;code&gt;master&lt;/code&gt; or &lt;code&gt;main&lt;/code&gt; branch, you&#039;ll need to
coordinate with everyone working in the repository.&lt;/p&gt;
&lt;p&gt;Let the others affected know that a secret was found that needs to be excised
from everyone&#039;s git history. When you edit the git history to remove a file, it
can cause problems with your teammatesl local clones; moreover, they can end up
re-inserting the secret back into the public repository when they push their work.
So it is important that everyone affected is in sync for the excision to work.
This means that everyone needs to stop what they are doing, close outstanding PRs,
and push up work that’s in progress.&lt;/p&gt;
&lt;p&gt;Now, let&#039;s make a fresh clone.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Delete your existing clone in its entirety.&lt;/li&gt;
&lt;li&gt;Make a fresh clone with &lt;code&gt;git clone [repository URL]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Change into the project directory with &lt;code&gt;cd [project name]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Download the entire repository history: &lt;code&gt;git pull --all --tags&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The last step will look a little bit familiar. &lt;code&gt;git pull&lt;/code&gt; tells git to grab updates
from the remote repository, and apply them in the current branch (when it makes sense
to do so, that is, when the local branch is set to track a remote branch). But git is
smart, it doesn’t pull everything down, only what’s needed. This is where the &lt;code&gt;--all&lt;/code&gt;
flag comes in. This flag tells git to grab every branch from the remote
repository. And the &lt;code&gt;--tags&lt;/code&gt; flag tells git to grab every tag as well. So this
command tells git to download the entire repository history, a complete and total
clone. We have to do this, because it is possible that the commit containing the
secret exists in more than one branch or tag. We need to ensure we don&#039;t just scrub
our secret from a portion of the repository history. As a result, this command can
take a very long time, if the repository is very large.&lt;/p&gt;
&lt;h2 id=&quot;The+secret+is+in+the+last+commit+and+there+is+nothing+else&quot; name=&quot;The+secret+is+in+the+last+commit+and+there+is+nothing+else&quot;&gt;The secret is in the last commit and there is nothing else&lt;/h2&gt;
&lt;p&gt;This is the simplest case.  In this case, we can drop the last commit in its
entirety. We do this with&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git reset --hard HEAD~1&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What does this command do? Let&#039;s break it apart a little bit. &lt;code&gt;git reset&lt;/code&gt; is how
we tell git that we want to undo recent changes. Normally, this command by itself
tells git to unstage anything we&#039;ve added with &lt;code&gt;git add&lt;/code&gt;, but haven&#039;t committed yet.
This version of resetting isn&#039;t sufficient for our purposes. But &lt;code&gt;git reset&lt;/code&gt; is more
flexible than that. We can tell git to take us back in time to a previous commit
as well.  We do that by telling git which commit to rewind to. We can use a
commit&#039;s identifier (it&#039;s “SHA”), or we can use an indirect reference. &lt;code&gt;HEAD&lt;/code&gt; is
what git calls the most recent commit on the checked out branch. &lt;code&gt;HEAD~1&lt;/code&gt; means
&amp;quot;the first commit prior to the most recent&amp;quot; (likewise &lt;code&gt;HEAD~2&lt;/code&gt; means &amp;quot;two
commits prior to the most recent&amp;quot;).  Finally, the &lt;code&gt;--hard&lt;/code&gt; tells git to throw away
any differences between the current state and the state we&#039;re resetting to. If you
leave off the &lt;code&gt;--hard&lt;/code&gt; your changes, including the secret, won&#039;t be discarded.
With &lt;code&gt;--hard&lt;/code&gt;, the differences will be deleted, gone forever (which is precisely
what we want!).&lt;/p&gt;
&lt;h2 id=&quot;The+secret+is+in+the+last+commit+but+there+are+other+changes&quot; name=&quot;The+secret+is+in+the+last+commit+but+there+are+other+changes&quot;&gt;The secret is in the last commit but there are other changes&lt;/h2&gt;
&lt;p&gt;In this case, we don&#039;t want to completely drop the last commit. We want to edit the
last commit instead. Edit your code to remove the secrets, and then add your changes
as usual. Then, instead of making a new commit, we&#039;ll tell git we want to amend the
previous one:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git add [FILENAME]
git commit --amend&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We all know &lt;code&gt;git commit&lt;/code&gt;, but the &lt;code&gt;--amend&lt;/code&gt; flag is our friend here. This tells git
that we want to edit the previous commit, rather than creating a new one. We can
continue to make changes to the last commit in this way, right up until we’re ready
to either push our work, or start on a new commit.&lt;/p&gt;
&lt;h2 id=&quot;The+secret+is+beyond+the+last+commit&quot; name=&quot;The+secret+is+beyond+the+last+commit&quot;&gt;The secret is beyond the last commit&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;It&#039;s complicated&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you know you committed a secret, but have since committed other changes, things
get trickier quickly. In anything but the simplest cases, we are going to want a
more powerful tool to help us do a deep clean of the repository We&#039;re going to use
&lt;a href=&quot;https://github.com/newren/git-filter-repo&quot;&gt;git-filter-repo&lt;/a&gt;, a tool recommended by the git maintainers that will help us
to rewrite history in a more user-friendly way than the native git tooling.&lt;/p&gt;
&lt;p&gt;A technical aside to those familiar with the concept of rebasing. (If you don&#039;t know
what that means, feel free to skip this paragraph.) All of the cases covered below
can of course be managed using native git tools, particularly by rebasing. Sometimes
rebasing is relatively painless, of course, but in the kinds of scenarios we&#039;re
presenting here, rebasing is going to be a tedious and deeply error-prone process.
This is why I am favoring purpose-built tools like &lt;a href=&quot;https://github.com/newren/git-filter-repo&quot;&gt;git-filter-repo&lt;/a&gt; over
rebasing. It is far better to avoid opening the possibility for making mistakes.
Experience shows, recovering from a botched rebase is extremely time consuming,
and often nearly impossible. Better to use the right tool for the job.&lt;/p&gt;
&lt;p&gt;First, install &lt;a href=&quot;https://github.com/newren/git-filter-repo&quot;&gt;git-filter-repo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Next, let&#039;s assess the situation to determine which technique is right for your
situation. Sometimes secrets are files, and sometimes secrets are lines of code.
For example, if you accidentally committed an SSH key or TLS certificate file,
these are contained in specialized files that you&#039;ll need to excise. On the other
hand, maybe you have a single line of code containing an API key that&#039;s part of a
larger source file. In that case, you want to modify one or more lines of a file
without deleting it.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/newren/git-filter-repo&quot;&gt;Git-filter-repo&lt;/a&gt; can handle both cases, but requires different syntax for
each case.&lt;/p&gt;
&lt;h3 id=&quot;Backup+your+config&quot; name=&quot;Backup+your+config&quot;&gt;Backup your config&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/newren/git-filter-repo&quot;&gt;git-filter-repo&lt;/a&gt; will modify your repository&#039;s &lt;code&gt;.git/config&lt;/code&gt; file to
prevent you from accidentantly pushing your changes.  So, make a backup
of your &lt;code&gt;git/config&lt;/code&gt; before proceeding.&lt;/p&gt;
&lt;h3 id=&quot;Excise+an+entire+file&quot; name=&quot;Excise+an+entire+file&quot;&gt;Excise an entire file&lt;/h3&gt;
&lt;p&gt;To tell &lt;a href=&quot;https://github.com/newren/git-filter-repo&quot;&gt;git-filter-repo&lt;/a&gt; to excise a file from the git history, we need only
a single command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git filter-repo --use-base-name --path [FILENAME] --invert-paths&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;--use-base-name&lt;/code&gt; option tells &lt;a href=&quot;https://github.com/newren/git-filter-repo&quot;&gt;git-filter-repo&lt;/a&gt; that we are specifying a
filename, and not a full path to a file. You can leave off this option if you
would rather specify the full path explicitly.&lt;/p&gt;
&lt;p&gt;Normally, &lt;a href=&quot;https://github.com/newren/git-filter-repo&quot;&gt;git-filter-repo&lt;/a&gt; works by ignoring the filenames specified (they
are, as the name suggests, filtered out). But we want the inverse behavior, we
want &lt;a href=&quot;https://github.com/newren/git-filter-repo&quot;&gt;git-filter-repo&lt;/a&gt; to ignore everything except the specified file. So we
must pass &lt;code&gt;--invert-paths&lt;/code&gt; to tell it this. If you leave off the &lt;code&gt;--invert-paths&lt;/code&gt;,
you&#039;ll excise everything except the specified file, which is the exact opposite of
what we want, and would likely be disastrous. Please don&#039;t do that.&lt;/p&gt;
&lt;h3 id=&quot;Edit+a+file+without+removing+it&quot; name=&quot;Edit+a+file+without+removing+it&quot;&gt;Edit a file without removing it&lt;/h3&gt;
&lt;p&gt;If you only need to edit one or more lines in a file without deleting the file,
&lt;a href=&quot;https://github.com/newren/git-filter-repo&quot;&gt;git-filter-repo&lt;/a&gt; takes a sequence of search-and-replace commands (optionally
using regular expressions).&lt;/p&gt;
&lt;p&gt;First, identify all the lines containing secrets that need to be excised. You&#039;ll
also need to work out a plan for how you will replace those lines. Perhaps just
deleting them is enough. But perhaps they need to be modified to prevent a runtime
crash.&lt;/p&gt;
&lt;p&gt;Next, create a file containing the search-and-replace commands, called
&lt;code&gt;replacements.txt&lt;/code&gt;. Make sure it’s in a folder outside of your repo, for example,
the parent folder.&lt;/p&gt;
&lt;p&gt;The format of this file is one search-and-replace command per line, using the
format:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ORIGINAL==&amp;gt;REPLACEMENT&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For example, suppose that you’ve hard-coded an API token into your code, like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;AUTH_TOKEN=’123abc’&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now suppose that you’ve decided that it’s better to load the API token from an
environment variable, as such:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;AUTH_TOKEN=ENV[‘AUTH_TOKEN’]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can tell &lt;a href=&quot;https://github.com/newren/git-filter-repo&quot;&gt;git-filter-repo&lt;/a&gt; to search for the hard-coded token, and replace
with the environment variable by adding this line to replacements.txt:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;‘123abc’==&amp;gt;ENV[‘AUTH_TOKEN’]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you have multiple secrets you need to excise, you can have more than one rule
like this in &lt;code&gt;replacements.txt&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Finally, assuming you placed &lt;code&gt;replacements.txt&lt;/code&gt; in the parent directory, we invoke
&lt;a href=&quot;https://github.com/newren/git-filter-repo&quot;&gt;git-filter-repo&lt;/a&gt; with our search-and-replace commands like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git filter-repo --replace-text ../replacements.txt&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sometimes you might get an error saying you’re not working from a clean clone.
That&#039;s OK. &lt;a href=&quot;https://github.com/newren/git-filter-repo&quot;&gt;Git-filter-repo&lt;/a&gt; is making irreversible changes to your local
repository, and it wants to be certain that you have a backup before it does that.
Of course, we do have a remote repository, and we&#039;re working from a local clone. And
of course we are very interested in making irreversible edits to our commit history -
we have a secret to purge! So there&#039;s no need for &lt;a href=&quot;https://github.com/newren/git-filter-repo&quot;&gt;git-filter-repo&lt;/a&gt; to worry.
We can reassure it that we are OK with making irreversible changes by adding the
&lt;code&gt;--force&lt;/code&gt; flag:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git filter-repo --replace-text ../replacements.txt --force&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And now you have a clean git history! You’ll want to validate your work by compiling
your software or running your test suite. Then once you’re satisfied that nothing is
broken, move on to the next step to propagate the new history to your remote repository
and the rest of the team.&lt;/p&gt;
&lt;h3 id=&quot;Restoring+your+config&quot; name=&quot;Restoring+your+config&quot;&gt;Restoring your config&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/newren/git-filter-repo&quot;&gt;git-filter-repo&lt;/a&gt; might have modified your repository&#039;s &lt;code&gt;.git/config&lt;/code&gt; file,
removing the remote repository configuration.  Restore your &lt;code&gt;.git/config&lt;/code&gt; otherwise
you won&#039;t be able to pubish to your remote repo.&lt;/p&gt;
&lt;h2 id=&quot;Publishing+your+repo&quot; name=&quot;Publishing+your+repo&quot;&gt;Publishing your repo&lt;/h2&gt;
&lt;p&gt;If you only just added the secret, and haven&#039;t pushed any of your work yet, you&#039;re
done. Just keep working like you had been, and no one will ever know. &lt;/p&gt;
&lt;p&gt;Otherwise, we&#039;ll need to overwrite what&#039;s on your remote git repository (such
as GitHub), as it still contains tainted history. We can&#039;t simply push, however:
The remote repository will refuse to accept our push because we’ve re-written
history. So we&#039;ll need to force push instead.  Moreover, if our re-writes to
history affect multiple branches or tags, we&#039;ll need to push them all up. We
can accomplish all of this like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git push --all --force &amp;amp;&amp;amp; git push --tags --force&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note: The &lt;code&gt;--all&lt;/code&gt; argument does not automatically push any updated tags. Git does not
allow the push arguments &lt;code&gt;--all&lt;/code&gt; and &lt;code&gt;--tags&lt;/code&gt; to be used in the same call. If you
need to run both commands, you can do so with the single line, thanks to the &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;Coordinate+with+your+team&quot; name=&quot;Coordinate+with+your+team&quot;&gt;Coordinate with your team&lt;/h2&gt;
&lt;p&gt;If you work as part of a team, now comes the hard part. Everyone you identified as
affected at the beginning of this process still has the old history. They need to
synchronize against the revised history you just force-pushed. This is where errors
can happen, and more importantly, where frustration can occur.&lt;/p&gt;
&lt;p&gt;Ideally everyone pushed their work up before you edited the history. In that case,
everyone can simply make a clean clone of the repo and pick up where they left off.&lt;/p&gt;
&lt;p&gt;But if someone failed to push their work up before you re-wrote history, they&#039;re going
to find they have a number of conflicts that need to be resolved when they pull.
Instead, they need to fetch the new history from the remote repository, and rebase
their hard work on the re-written history. To do this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git fetch
git rebase -i origin/[branchname]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you aren&#039;t familiar with &lt;code&gt;git fetch&lt;/code&gt;, this command tells git to download new data
from the remote repository, but unlike &lt;code&gt;git pull&lt;/code&gt;, it doesn&#039;t attempt to merge new
commits into your current working branch. So the fetch here is requesting all the
newly re-written history.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In general, this is a pain in the butt, so it is better
to coordinate with the team to avoid this step.&lt;/p&gt;
&lt;/blockquote&gt;</content>
</entry>
<entry>
<title>Baremetal backup</title>
<link href="https://www.0ink.net/posts/2025/2025-06-05-bm-imaging.html"></link>
<id>urn:uuid:307994e0-be0d-b9ae-3f87-83be9a5be9e8</id>
<updated>2025-05-31T00:00:00+02:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Preparation

Disabling BitLocker
Get Clonezilla

...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Preparation&quot;&gt;Preparation&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Disabling+BitLocker&quot;&gt;Disabling BitLocker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Get+Clonezilla&quot;&gt;Get Clonezilla&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Create+boot+media&quot;&gt;Create boot media&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Running+Clonezilla&quot;&gt;Running Clonezilla&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/clonezilla/clonezilla_logo.png&quot; alt=&quot;logo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I recently bought a used laptop.  Before using it, I wanted to image the disk so that I can
restore it to a pristine state.  For that I am using &lt;a href=&quot;https://clonezilla.org/&quot;&gt;Clonezilla&lt;/a&gt;.  &lt;a href=&quot;https://clonezilla.org/&quot;&gt;Clonezilla&lt;/a&gt; is
a partition and disk imaging/cloning program similar to &lt;a href=&quot;http://en.wikipedia.org/wiki/Acronis_True_Image&quot;&gt;True Image&lt;/a&gt; or &lt;a href=&quot;https://en.wikipedia.org/wiki/Ghost_(disk_utility)&quot;&gt;Norton Ghost&lt;/a&gt;.
It helps you to do system deployment, bare metal backup and recovery. Three types of Clonezilla
are available, &lt;a href=&quot;https://clonezilla.org/clonezilla-live.php&quot;&gt;Clonezilla live&lt;/a&gt;, &lt;a href=&quot;https://clonezilla.org/show-live-doc-content.php?topic=clonezilla-live/doc/11_lite_server&quot;&gt;Clonezilla lite server&lt;/a&gt;, and
&lt;a href=&quot;https://clonezilla.org/clonezilla-SE/&quot;&gt;Clonezilla SE (server edition)&lt;/a&gt;. Clonezilla live is suitable for single machine backup and
restore and that is what I am using here.  The other versions are used for mass system
deployments.&lt;/p&gt;
&lt;h2 id=&quot;Preparation&quot; name=&quot;Preparation&quot;&gt;Preparation&lt;/h2&gt;
&lt;p&gt;For this you need to prepare a USB thumb drive that will contain the Clonezilla software
and also accept the baremetal backup.  Clonezilla is smart enough to recognize filesystem
structure and will only backup up used blocks so the size of the thumbdrive only needs
to be as large as the used blocks.  When I tested this with a November 2024 Windows 11
image, a 32 GB drive was enough.&lt;/p&gt;
&lt;p&gt;In my case, my laptop came with BitLocker enabled.  Clonezilla doesn&#039;t understand BitLocker
drives, so you need to disable it first.&lt;/p&gt;
&lt;h3 id=&quot;Disabling+BitLocker&quot; name=&quot;Disabling+BitLocker&quot;&gt;Disabling BitLocker&lt;/h3&gt;
&lt;p&gt;Boot the PC.  In the Setup screens, press &lt;kbd&gt;Shift+F10&lt;/kbd&gt; to open a Command Prompt.&lt;/p&gt;
&lt;p&gt;Enter the command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;manage-bde -status&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To check the status of BitLocker.  To disable BitLocker enter:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;manage-bde -off C:&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where &lt;code&gt;C:&lt;/code&gt; is the drive where BitLocker is enabled.  This will start the
BitLocker decryption and can take some time.&lt;/p&gt;
&lt;h3 id=&quot;Get+Clonezilla&quot; name=&quot;Get+Clonezilla&quot;&gt;Get Clonezilla&lt;/h3&gt;
&lt;p&gt;Go to the &lt;a href=&quot;https://clonezilla.org/downloads/download.php&quot;&gt;Clonezilla download page&lt;/a&gt;, and select the &lt;strong&gt;stable&lt;/strong&gt;  branch, since I
assume you are using a modern PC, you probably will be using a UEFI firmware.  So select
&lt;strong&gt;AMD64&lt;/strong&gt; and &lt;strong&gt;zip&lt;/strong&gt; file type.&lt;/p&gt;
&lt;p&gt;Supposely the Ubuntu based version have Kernel that includes non-free firmware which should
have wider compatibility.  In 2025, this is rare, so using the Debian based version is
enough.&lt;/p&gt;
&lt;h2 id=&quot;Create+boot+media&quot; name=&quot;Create+boot+media&quot;&gt;Create boot media&lt;/h2&gt;
&lt;p&gt;So with the USB drive you must first partition it.  I would create a EFI partition to
contain the &lt;a href=&quot;https://clonezilla.org/&quot;&gt;Clonezilla&lt;/a&gt; software and a data partition to keep the backup image.&lt;/p&gt;
&lt;p&gt;If using &lt;code&gt;sfdisk&lt;/code&gt; you can use these commands:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sfdisk /dev/sde
&amp;gt; label: gpt
&amp;gt; ;500M;U;*
&amp;gt; ;;L;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sfdisk /dev/sde&lt;/code&gt; is the command with &lt;code&gt;/dev/sde&lt;/code&gt; being the drive we want to
partition.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;label: gpt&lt;/code&gt; specifies that we want to use &lt;code&gt;GPT&lt;/code&gt; partitioning since we are assuming
an UEFI firmware.  &lt;code&gt;sfdisk&lt;/code&gt; would normally default to &lt;code&gt;dos&lt;/code&gt; which generates an
Master Boot Record (MBR) partition instead.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;;500M;U;*&lt;/code&gt; : Define 1s partition as a 500M bootable EFI system partition.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;;;L;&lt;/code&gt; : Define the remaining space as a Linux partition.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You don&#039;t have to use &lt;code&gt;sfdisk&lt;/code&gt;, this is just an example.&lt;/p&gt;
&lt;p&gt;Next you need to create a FAT32 filesystem for the UEFI boot partition.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkfs.vfat \
    -F 32 \
    -n &quot;CZILLA&quot; \
    -v \
    /dev/sde1&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;mkfs.vfat&lt;/code&gt; : Create filesystem command&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-F 32&lt;/code&gt; : Specify that we are creating a &lt;code&gt;FAT32&lt;/code&gt; filesystem&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-n &quot;EFISYS&quot;&lt;/code&gt; : Name of the volume.  Can be anything.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-v&lt;/code&gt; : verbose flag&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/dev/sde1&lt;/code&gt; : Drive partition we are formatting&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Create the filesystem for the data:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkfs.ext4 \
      -b 4096 \
      -E stride=16,stripe-width=16 \
      -i 8192 \
      -m 0 \
      -L data \
      -O ^has_journal \
      /dev/sde2&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;mkfs.ext4&lt;/code&gt; : Create filesystem command&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-b 4096&lt;/code&gt; : Use 4K blocks.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-E stride=16,stripe-width=16&lt;/code&gt; : Options recommended for flash media&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-i 8192&lt;/code&gt; : i-node density, 8192 is a good number since for image backups we only have
a few large files.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-m 0&lt;/code&gt; : Reserved space set to &lt;code&gt;0&lt;/code&gt;.  Recommended for removable media.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-L data&lt;/code&gt; : Volume label.  Can be anything.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-O ^has_journal&lt;/code&gt; : Disable journaling, recommeded for flash media.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Copy the &lt;a href=&quot;https://clonezilla.org/&quot;&gt;Clonezilla&lt;/a&gt; software:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mount -t vfat /dev/sde1 /mnt
unzip clonezilla-live-3.2.2-5-amd64.zip -d /mnt
umount /mnt&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;mount -t vfat /dev/sde1 /mnt&lt;/code&gt; : Mounts the EFI filesystem in the &lt;code&gt;/mnt&lt;/code&gt; directory.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;unzip clonezilla-live-3.2.2-5-amd64.zip -d /mnt&lt;/code&gt; extracs the Zip file to the USB disk&lt;/li&gt;
&lt;li&gt;&lt;code&gt;umount /mnt&lt;/code&gt; : Unmount the filesystem&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At this point we are ready to image the baremetal system.&lt;/p&gt;
&lt;p&gt;You can use this
&lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/snippets/2025/clonezilla/clonezilla.sh&quot;&gt;script&lt;/a&gt;
to automate this step.&lt;/p&gt;
&lt;h2 id=&quot;Running+Clonezilla&quot; name=&quot;Running+Clonezilla&quot;&gt;Running Clonezilla&lt;/h2&gt;
&lt;p&gt;Insert the USB drive into one of your PCs USB ports, and turn on your PC.  You may
need to get the boot menu.  Usually this is done by pressing &lt;kbd&gt;Escape&lt;/kbd&gt; or &lt;kbd&gt;Delete&lt;/kbd&gt;
keys while the system is booting.  Select to boot from the USB drive.&lt;/p&gt;
&lt;p&gt;You will be shown the GRUB Boot menu:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/clonezilla/01.grubmenu.png&quot; alt=&quot;grubmenu&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Since I am old, I would choose the &lt;code&gt;large font&lt;/code&gt; option.  Just choose any option that works for you.
The system will first let you configure the keyboard and display.  I just leave things
as default.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/clonezilla/02.start_menu.png&quot; alt=&quot;start-menu&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Start &lt;strong&gt;Clonezilla&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/clonezilla/03.device-image.png&quot; alt=&quot;device-image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Select &lt;strong&gt;device-image&lt;/strong&gt; on the next menu.  This option is for backing up from a device
to a image file in a mounted drive.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/clonezilla/04.image-dir-select.png&quot; alt=&quot;image-dir-select&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Select &lt;strong&gt;local_dev&lt;/strong&gt;, since we want to save the image to the locally connected boot disk.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/clonezilla/05.find-drive.png&quot; alt=&quot;find-drive&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This screen gives you an opportunity to insert a removable drive.  Usually it will recognized
automatically shown here.  Once you see the drive, press &lt;kbd&gt;Control+C&lt;/kbd&gt;.  Since we are using
the same USB disk used for &lt;a href=&quot;https://clonezilla.org/&quot;&gt;Clonezilla&lt;/a&gt; you should be able to press &lt;kbd&gt;Control+C&lt;/kbd&gt; right
away.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/clonezilla/06.select-partition.png&quot; alt=&quot;select-partition&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Select the partition to store images.  In this example we are using
&lt;strong&gt;sdb2 31.5G|ext4|data&lt;/strong&gt; partition.  Remember, we used the &lt;code&gt;-L data&lt;/code&gt; option when
formatting the data file system.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/clonezilla/07.ask-fsck.png&quot; alt=&quot;ask-fsck&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Since we are using newly initialized drives, we can safely use the &lt;strong&gt;no-fsck&lt;/strong&gt; option.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/clonezilla/08.ask-dir.png&quot; alt=&quot;ask-dir&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You can select a different directory in the data partition to store images.  Just leave
as default.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/clonezilla/09.beginner-or-expert.png&quot; alt=&quot;beginner-or-expert&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If you have to read this article, you are &lt;em&gt;NO&lt;/em&gt; expert, so select &lt;strong&gt;beginner&lt;/strong&gt; mode.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/clonezilla/10.savemode.png&quot; alt=&quot;save-mode&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Since we want to make a full backup, select the &lt;strong&gt;savedisk&lt;/strong&gt; option.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/clonezilla/11.input-name.png&quot; alt=&quot;input-name&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You can change the image name here.  I simply leave it as default.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/clonezilla/12.select-drive.png&quot; alt=&quot;select-drive&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Pick the right disk drive to backup...&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/clonezilla/13.compression.png&quot; alt=&quot;compression&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Choose the compression level.  Normally just select &lt;strong&gt;-z9p&lt;/strong&gt; as that would
generate the smallest file size.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/clonezilla/14.ask-fsck-again.png&quot; alt=&quot;ask-fsck-again&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Since we are backing up a Windows systems, the only possible option is &lt;strong&gt;sfsck&lt;/strong&gt;.
Only if you suspect that the filesystem is not in a good state you should use
one of the remaining options.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/clonezilla/15.check-image.png&quot; alt=&quot;check-image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Enable &lt;strong&gt;check the saved image&lt;/strong&gt;.  It is a good idea to always do this
to make sure the the backup was stored correctly.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/clonezilla/16.ask-encrypt.png&quot; alt=&quot;ask-encrypt&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I normally would skip the encryption step as the backup does not contain any
sensitive data and I store it off-line.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/clonezilla/17.save-logs.png&quot; alt=&quot;save-logs&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Keep this default (copy log files).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/clonezilla/18.done-action.png&quot; alt=&quot;done-action&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Leave this default, which is to ask what to do once things are done.&lt;/p&gt;
&lt;p&gt;After some additional prompts that you may need to acknowledge you will see this screen:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/clonezilla/19.cloning-progress.png&quot; alt=&quot;cloning-progress&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Depending on the disk size, this may take awhile.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/clonezilla/20.check-image-progress.png&quot; alt=&quot;check-image-progress&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Since we selected &lt;strong&gt;check the saved image&lt;/strong&gt;, you will see a &lt;strong&gt;checking image&lt;/strong&gt; screen.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/clonezilla/22.done.png&quot; alt=&quot;done&quot; /&gt;&lt;/p&gt;
&lt;p&gt;After the backup is completed, you will get this screen.  Hit &lt;kbd&gt;Enter&lt;/kbd&gt; to continue.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/clonezilla/23.final-menu.png&quot; alt=&quot;final-menu&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Just &lt;strong&gt;poweroff&lt;/strong&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>tcpdump snippets</title>
<link href="https://www.0ink.net/posts/2025/2025-06-01-tcpdump.html"></link>
<id>urn:uuid:f2e3232f-ff82-0371-c132-1958726fabe8</id>
<updated>2024-12-12T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
List interfaces
GUI

Capture file rotation
stftime codes
...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#List+interfaces&quot;&gt;List interfaces&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#GUI&quot;&gt;GUI&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Capture+file+rotation&quot;&gt;Capture file rotation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#stftime+codes&quot;&gt;stftime codes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Filter+by+MAC+address&quot;&gt;Filter by MAC address&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Monitor+DHCP+network+traffic&quot;&gt;Monitor DHCP network traffic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Monitoring+LLDP+traffic&quot;&gt;Monitoring LLDP traffic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Showing+vlan+traffic&quot;&gt;Showing vlan traffic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Filtering+tagged%2Funtagged+VLAN+%28IEEE+802.1Q%29+traffic&quot;&gt;Filtering tagged/untagged VLAN (IEEE 802.1Q) traffic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Using+ngrep&quot;&gt;Using ngrep&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Piping+tcpdump+output&quot;&gt;Piping tcpdump output&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Printing+packets+in+ASCII+or+Hex&quot;&gt;Printing packets in ASCII or Hex&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Quieter&quot;&gt;Quieter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/tcpdump.png&quot; alt=&quot;tcpdump icon&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://hackertarget.com/tcpdump-examples/&quot;&gt;https://hackertarget.com/tcpdump-examples/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://danielmiessler.com/p/tcpdump/&quot;&gt;https://danielmiessler.com/p/tcpdump/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.redhat.com/en/blog/tcpdump-part-2&quot;&gt;https://www.redhat.com/en/blog/tcpdump-part-2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://edoceo.com/sys/tcpdump&quot;&gt;https://edoceo.com/sys/tcpdump&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/tcpdump-examples/how-to-use-tcpdump&quot;&gt;https://github.com/tcpdump-examples/how-to-use-tcpdump&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://community.exabeam.com/s/article/tcpdump-cheat-sheet&quot;&gt;https://community.exabeam.com/s/article/tcpdump-cheat-sheet&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some useful stuff related to using &lt;a href=&quot;http://tcpdump.org/&quot;&gt;tcpdump&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://tcpdump.org/&quot;&gt;tcpdump&lt;/a&gt; is a data-network packet analyzer computer program that runs
under a command line interface. It allows the user to display TCP/IP and other
packets being transmitted or received over a network to which the computer is
attached.&lt;/p&gt;
&lt;p&gt;Tcpdump works on most Unix-like operating systems: Linux, Solaris, FreeBSD,
DragonFly BSD, NetBSD, OpenBSD, OpenWrt, macOS, HP-UX 11i, and AIX. In those
systems, &lt;a href=&quot;http://tcpdump.org/&quot;&gt;tcpdump&lt;/a&gt; uses the libpcap library to capture packets. The port of
&lt;a href=&quot;http://tcpdump.org/&quot;&gt;tcpdump&lt;/a&gt; for Windows is called WinDump; it uses WinPcap, the Windows
ersion of libpcap.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/sniffing.png&quot; alt=&quot;Packet Sniffing&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;List+interfaces&quot; name=&quot;List+interfaces&quot;&gt;List interfaces&lt;/h2&gt;
&lt;p&gt;To list the interfaces available for sniffing you can use this option:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;tcpdump -D&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example output:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1.enp1s0 [Up, Running, Connected]
2.any (Pseudo-device that captures on all interfaces) [Up, Running]
3.lo [Up, Running, Loopback]
4.eno1 [Up, Disconnected]
5.virbr0 [Up, Disconnected]
6.docker0 [Up, Disconnected]
7.nflog (Linux netfilter log (NFLOG) interface) [none]
8.nfqueue (Linux netfilter queue (NFQUEUE) interface) [none]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;-D&lt;/code&gt; or &lt;code&gt;--list-interfaces&lt;/code&gt; option prints the list of the network interfaces
available on the system and on which tcpdump can capture packets.  For each network
interface, a number and an interface name, possibly followed by
a text description of the interface, are printed.  The interface
name or the number can be supplied to the &lt;code&gt;-i&lt;/code&gt; flag to specify an
interface on which to capture.&lt;/p&gt;
&lt;p&gt;This can be useful on systems that don&#039;t have a command to list
them (e.g., Windows systems, or UNIX systems lacking &lt;code&gt;ifconfig -a&lt;/code&gt;); the number can be useful on Windows 2000 and later systems,
where the interface name is a somewhat complex string.&lt;/p&gt;
&lt;h2 id=&quot;GUI&quot; name=&quot;GUI&quot;&gt;GUI&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://tcpdump.org/&quot;&gt;tcpdump&lt;/a&gt; is a command line tool.  If you prefer to have a full GUI
interface you can use &lt;a href=&quot;https://www.wireshark.org/&quot;&gt;Wireshark&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/wireshark.png&quot; alt=&quot;Wireshark logo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.wireshark.org/&quot;&gt;Wireshark&lt;/a&gt; is an open-source packet analyzer.  It is used for network troubleshooting,
analysis, software and communications protocol development, and education. Originally
named Ethereal, the project was renamed &lt;a href=&quot;https://www.wireshark.org/&quot;&gt;Wireshark&lt;/a&gt; in May 2006 due to trademark issues.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.wireshark.org/&quot;&gt;Wireshark&lt;/a&gt; is cross-platform, using the Qt widget toolkit in current releases to
implement its user interface, and using pcap to capture packets; it runs on Linux,
macOS, BSD, Solaris, some other Unix-like operating systems, and Microsoft Windows.
There is also a terminal-based (non-GUI) version called TShark.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/ws_screenshot.png&quot; alt=&quot;Wireshark UI&quot; /&gt;&lt;/p&gt;
&lt;p&gt;A feature of Wireshark is that it can also analyze capture traces from &lt;a href=&quot;http://tcpdump.org/&quot;&gt;tcpdump&lt;/a&gt;.
See &lt;a href=&quot;https://www.wireshark.org/docs/wsug_html_chunked/AppToolstcpdump.html&quot;&gt;Capturing with “tcpdump” for viewing with Wireshark&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The use case is for example you have a router, switch or a headless system.  You can
use &lt;a href=&quot;http://tcpdump.org/&quot;&gt;tcpdump&lt;/a&gt; to capture traffic and analyze it off-line by reading it into
&lt;a href=&quot;https://www.wireshark.org/&quot;&gt;Wireshark&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To capture for off-line analysis use the command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;tcpdump -i &amp;lt;interface&amp;gt; -s 65535 -w &amp;lt;file&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Older versions of &lt;a href=&quot;http://tcpdump.org/&quot;&gt;tcpdump&lt;/a&gt; when writing to file would truncate packets to 68 or 96 bytes.
Current versions default to 262144 bytes.  Use the &lt;code&gt;-s&lt;/code&gt; options to set the max packet size to
capture, or use the &lt;code&gt;-S&lt;/code&gt; option to get the entire packet.  Since this impacts the total size of the file, you can use it to tweak the disk
usage.  You can pair this with option &lt;code&gt;-c&lt;/code&gt; to specify the number of packets to capture.&lt;/p&gt;
&lt;p&gt;You can use &lt;a href=&quot;http://tcpdump.org/&quot;&gt;tcpdump&lt;/a&gt; &lt;code&gt;-r&lt;/code&gt; option to analyze captured traffic files.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;# tcpdump -r dns.pcap
reading from file dns.pcap, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144
Warning: interface names might be incorrect
dropped privs to tcpdump
20:33:45.240421 wlp0s20f3 Out IP kkulkarni.attlocal.net.37376 &amp;gt; dsldevice.attlocal.net.domain: 8860+ PTR? 89.1.168.192.in-addr.arpa. (43)
20:33:45.250107 wlp0s20f3 In  IP dsldevice.attlocal.net.domain &amp;gt; kkulkarni.attlocal.net.37376: 8860* 1/0/0 PTR kkulkarni.attlocal.net. (79)
20:33:45.253418 wlp0s20f3 Out IP kkulkarni.attlocal.net.54366 &amp;gt; dsldevice.attlocal.net.domain: 23092+ PTR? 1.112.168.192.in-addr.arpa. (44)
20:33:45.260212 wlp0s20f3 In  IP dsldevice.attlocal.net.domain &amp;gt; kkulkarni.attlocal.net.54366: 23092 NXDomain* 0/0/0 (44)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Capture+file+rotation&quot; name=&quot;Capture+file+rotation&quot;&gt;Capture file rotation&lt;/h3&gt;
&lt;p&gt;You can make &lt;a href=&quot;http://tcpdump.org/&quot;&gt;tcpdump&lt;/a&gt; automatically rotate files.  Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;tcpdump -i en0 -G 1800 -C 100 -w /var/tmp/trace-%H-%M.pcap&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-C&lt;/code&gt; &lt;em&gt;filesize&lt;/em&gt; &lt;br /&gt;Before writing a raw packet to a savefile, check whether the file is
currently larger than &lt;em&gt;filesize&lt;/em&gt; and, if so, close the current
savefile and open a new one.  Savefiles after the first savefile will
have the name specified with the &lt;code&gt;-w&lt;/code&gt;
flag, with a number after it, starting at 1 and continuing upward.
The units of &lt;em&gt;filesize&lt;/em&gt; are millions of bytes (1,000,000 bytes,
not 1,048,576 bytes).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-G&lt;/code&gt; &lt;em&gt;rotateseconds&lt;/em&gt; &lt;br /&gt;If specified, rotates the dump file specified with the &lt;code&gt;-w&lt;/code&gt;
option every &lt;em&gt;rotateseconds&lt;/em&gt; seconds.
Savefiles will have the name specified by &lt;code&gt;-w&lt;/code&gt;
which should include a time format as defined by
&lt;a href=&quot;https://pubs.opengroup.org/onlinepubs/009696799/functions/strftime.html&quot;&gt;strftime (3)&lt;/a&gt;. &lt;br /&gt;If no time format is specified, each new file will overwrite the previous.
Whenever a generated filename is not unique, tcpdump will overwrite the
preexisting data; providing a time specification that is coarser than the
capture period is therefore not advised. &lt;br /&gt;If used in conjunction with the &lt;code&gt;-C&lt;/code&gt;
option, filenames will take the form of &lt;em&gt;file&lt;/em&gt;&lt;code&gt;&amp;lt;count&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-W&lt;/code&gt; &lt;br /&gt;Used in conjunction with the &lt;code&gt;-C&lt;/code&gt;
option, this will limit the number
of files created to the specified number, and begin overwriting files
from the beginning, thus creating a &#039;rotating&#039; buffer.
In addition, it will name
the files with enough leading 0s to support the maximum number of
files, allowing them to sort correctly. &lt;br /&gt;Used in conjunction with the &lt;code&gt;-G&lt;/code&gt;
option, this will limit the number of rotated dump files that get
created, exiting with status 0 when reaching the limit. &lt;br /&gt;If used in conjunction with both &lt;code&gt;-C&lt;/code&gt; and &lt;code&gt;-G&lt;/code&gt;, the &lt;code&gt;-W&lt;/code&gt;
option will currently be ignored, and will only affect the file name.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;stftime+codes&quot; name=&quot;stftime+codes&quot;&gt;stftime codes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;%m=month&lt;/li&gt;
&lt;li&gt;%d=day of month&lt;/li&gt;
&lt;li&gt;%H=hour of day&lt;/li&gt;
&lt;li&gt;%M=minute of day&lt;/li&gt;
&lt;li&gt;%S=second of day&lt;/li&gt;
&lt;li&gt;%s=millisecond of day&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Filter+by+MAC+address&quot; name=&quot;Filter+by+MAC+address&quot;&gt;Filter by MAC address&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://tcpdump.org/&quot;&gt;tcpdump&lt;/a&gt; supports the &lt;code&gt;ether&lt;/code&gt; qualifier to specify ethernet addresses in the
standard colon-separated format. For example, to capture any broadcast traffic,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;$ tcpdump ether dst ff:ff:ff:ff:ff:ff&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To capture any traffic sent to or from a given MAC address,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;$ tcpdump ether host e8:2a:ea:44:55:66&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(Here the first three octets identify the MAC in question as belonging to an
Intel NIC, &lt;code&gt;e8:2a:ea&lt;/code&gt; being an OUI assigned to Intel.)&lt;/p&gt;
&lt;p&gt;Reference: &lt;a href=&quot;https://www.pico.net/kb/how-does-one-filter-mac-addresses-using-tcpdump/&quot;&gt;https://www.pico.net/kb/how-does-one-filter-mac-addresses-using-tcpdump/&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;Monitor+DHCP+network+traffic&quot; name=&quot;Monitor+DHCP+network+traffic&quot;&gt;Monitor DHCP network traffic&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;http://tcpdump.org/&quot;&gt;tcpdump&lt;/a&gt; command can be used to monitor DHCP related network traffic.
This is very useful in cases where DHCP issues may have to be investigated.
Basically, the &lt;a href=&quot;http://tcpdump.org/&quot;&gt;tcpdump&lt;/a&gt; command can be used to do some packet sniffing on
the network.&lt;/p&gt;
&lt;p&gt;The method to capture DHCP traffic is to define a filter so that tcpdump
dumps only DHCP related traffic. In DHCP, UDP port number 67 is used by a DHCP
server, and UDP port number 68 is used by DHCP clients. Thus, you want to capture
traffic with port number 67 or 68 as follows, assuming that &lt;code&gt;eth0&lt;/code&gt; is the
network interface that will be used to monitor:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;# tcpdump -i eth0 port 67 or port 68 -e -n -vv&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By using the &lt;code&gt;-vv&lt;/code&gt; option (for very verbose) you may see a lengthy output
from the tcpdump command displaying a lot of information. In it you can see
which DHCP server is responding, and what IP address is assigned. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Client-ID Option 61, length 7: ether ec:9b:f3:6b:97:4b
Requested-IP Option 50, length 4: 192.168.0.3
Server-ID Option 54, length 4: 192.168.0.1
MSZ Option 57, length 2: 1500
Vendor-Class Option 60, length 16: &quot;android-dhcp-7.0&quot;
Hostname Option 12, length 16: &quot;SAMSUNG-SM-G890A&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the example above, you can see that a Samsung SM-G890A phone, running
Android, gets IP address 192.168.0.3 assigned from DHCP server 192.168.0.1.
You can also see the MAC (or &amp;quot;hardware&amp;quot;) address of the phone: &lt;code&gt;ec:9b:f3:6b:97:4a&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;One you&#039;re finished sniffing the network for DHCP related traffic, you can
simply &lt;code&gt;CTRL-C&lt;/code&gt; out of the tcpdump command.&lt;/p&gt;
&lt;p&gt;Reference: &lt;a href=&quot;https://unixhealthcheck.com/blog?id=433&quot;&gt;https://unixhealthcheck.com/blog?id=433&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;For IPv6 the command is slightly different:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# tcpdump -i &amp;lt;interface name&amp;gt; -n -vv &#039;(udp port 546 or 547) or icmp6&#039;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In IPv6, DHCP is using different ports.  Also, some of the configuration
items are sent via ICMP6.&lt;/p&gt;
&lt;h2 id=&quot;Monitoring+LLDP+traffic&quot; name=&quot;Monitoring+LLDP+traffic&quot;&gt;Monitoring LLDP traffic&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;tcpdump -i eth0 -v -e ether proto 0x88cc &lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-i&lt;/code&gt; &lt;em&gt;nic&lt;/em&gt; : Select the interface&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-v&lt;/code&gt; or &lt;code&gt;-vv&lt;/code&gt; : more protocol decoding&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-e&lt;/code&gt; : Show Ethernet Frame fields&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ether proto 0x88cc&lt;/code&gt; : select LLDP frames (Protocol ID &lt;code&gt;0x88cc&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Showing+vlan+traffic&quot; name=&quot;Showing+vlan+traffic&quot;&gt;Showing vlan traffic&lt;/h2&gt;
&lt;p&gt;You can verify the incoming traffic to see if they have VLAN tags by using &lt;a href=&quot;http://tcpdump.org/&quot;&gt;tcpdump&lt;/a&gt; with
the &lt;code&gt;-e&lt;/code&gt; and &lt;code&gt;vlan&lt;/code&gt; option.&lt;/p&gt;
&lt;p&gt;This will show the details of the VLAN header:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tcpdump -i bond0 -nn -e  vlan&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reference: &lt;a href=&quot;https://access.redhat.com/solutions/2630851&quot;&gt;https://access.redhat.com/solutions/2630851&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;Filtering+tagged%2Funtagged+VLAN+%28IEEE+802.1Q%29+traffic&quot; name=&quot;Filtering+tagged%2Funtagged+VLAN+%28IEEE+802.1Q%29+traffic&quot;&gt;Filtering tagged/untagged VLAN (IEEE 802.1Q) traffic&lt;/h2&gt;
&lt;p&gt;If you want use a &lt;a href=&quot;http://tcpdump.org/&quot;&gt;tcpdump&lt;/a&gt; filter that matches both tagged and
untagged traffic you have to watch out for the fact that
using VLAN changes the way the traffic is encapsulated.  The issue is that
tagging traffic inserts four more bytes (namely the VLAN ID) to the ethernet (or
more precisely IEEE 802.1Q) header. Without specifically asking for VLAN traffic
in the BPF filter, every traffic is parsed as untagged traffic. Thus, the specified
filter delivers only untagged UDP packets (i.e., their frames) and drops all tagged
traffic.&lt;/p&gt;
&lt;p&gt;Now watch out: similar things happen if you specify the mysterious &lt;code&gt;vlan&lt;/code&gt; keyword
in the tcpdump filter. After specifiying the &lt;code&gt;vlan&lt;/code&gt; keyword, the &lt;em&gt;subsequent&lt;/em&gt;
filters are matched against traffic shifted by 4 bytes to the right. Note that this
is also true if you specify &lt;code&gt;not vlan&lt;/code&gt; as filter.&lt;/p&gt;
&lt;p&gt;If we want to match both tagged and untagged
UDP traffic, we have to specify the following filter:&lt;/p&gt;
&lt;p&gt;Filter UDP traffic, both VLAN tagged and untagged:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;tcpdump -nn -d &quot;udp or (vlan and udp)&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or, the generic solution:&lt;/p&gt;
&lt;p&gt;Generic filter expression that matches VLAN tagged and untagged traffic:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;tcpdump -nn -d &quot;&amp;lt;filter&amp;gt; or (vlan and &amp;lt;filter&amp;gt;)&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you want to filter only untagged traffic, specify the following:&lt;/p&gt;
&lt;p&gt;Generic filter to match only untagged traffic:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;tcpdump -nn -d &amp;lt;filter&amp;gt; and not vlan&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Long story short: When using tcpdump (or libpcap), be careful where to put the
&lt;code&gt;vlan&lt;/code&gt; keyword in your expression. In general, it&#039;s a very bad idea to specify the
keyword twice, unless you pack VLAN traffic into VLAN traffic. Maybe these examples
are more explanative than the quote below taken from the &lt;a href=&quot;http://tcpdump.org/&quot;&gt;tcpdump&lt;/a&gt;
manpage:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note that the first vlan keyword encountered in expression changes the decoding
offsets for the remainder of expression on the assumption that the packet is a
VLAN packet.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Recall this (admittedly sometimes strange) behavior is not a bug...&lt;/p&gt;
&lt;p&gt;Reference: &lt;a href=&quot;https://www.christian-rossow.de/articles/tcpdump_filter_mixed_tagged_and_untagged_VLAN_traffic.php&quot;&gt;https://www.christian-rossow.de/articles/tcpdump_filter_mixed_tagged_and_untagged_VLAN_traffic.php&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;Using+ngrep&quot; name=&quot;Using+ngrep&quot;&gt;Using ngrep&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://tcpdump.org/&quot;&gt;tcpdump&lt;/a&gt; uses &lt;a href=&quot;https://www.tcpdump.org/manpages/pcap-filter.7.html&quot;&gt;berkeley packet filter&lt;/a&gt;
expressions to select the packets to analyze.  If you prefer to use &lt;a href=&quot;https://en.wikipedia.org/wiki/Regular_expression&quot;&gt;regular expressions&lt;/a&gt; you
can use &lt;a href=&quot;https://github.com/jpr5/ngrep/&quot;&gt;ngrep&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/jpr5/ngrep/&quot;&gt;ngrep&lt;/a&gt; (network grep) is a network packet analyzer. It has a command-line interface, and
relies upon the pcap library and the GNU regex library.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/jpr5/ngrep/&quot;&gt;ngrep&lt;/a&gt; supports Berkeley Packet Filter (BPF) logic to select network sources or
destinations or protocols, and also allows matching patterns or regular expressions in the
data payload of packets using GNU grep syntax, showing packet data in a human-friendly way. &lt;/p&gt;
&lt;p&gt;Examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Capture network traffic incoming/outgoing to/from eth0 interface and show parameters following
HTTP (TCP/80) GET or POST methods:
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ngrep -l -q -d eth0 -i &quot;^GET |^POST &quot; tcp and port 80&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Capture network traffic incoming/outgoing to/from eth0 interface and show the HTTP (TCP/80)
User-Agent string
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ngrep -l -q -d eth0 -i &quot;User-Agent: &quot; tcp and port 80&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Capture network traffic incoming/outgoing to/from eth0 interface and show the DNS (UDP/53)
querys and responses
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ngrep -l -q -d eth0 -i &quot;&quot; udp and port 53&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Piping+tcpdump+output&quot; name=&quot;Piping+tcpdump+output&quot;&gt;Piping tcpdump output&lt;/h2&gt;
&lt;p&gt;When piping tcpdump output to another command you should enable line buffered mode with &lt;code&gt;-l&lt;/code&gt; or &lt;code&gt;-C&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Without the option to force line (&lt;code&gt;-l&lt;/code&gt;) buffered (or packet buffered &lt;code&gt;-C&lt;/code&gt;) mode you will not
always get the expected response when piping. By using this option the output is sent
immediately to the piped command.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;tcpdump -nl | awk &#039;/10.14.34.132/&#039;
tcpdump -i eth0 -s0 -l port 80 | awk &#039;/Server:/&#039;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ref: &lt;a href=&quot;https://community.exabeam.com/s/article/tcpdump-cheat-sheet&quot;&gt;https://community.exabeam.com/s/article/tcpdump-cheat-sheet&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;Printing+packets+in+ASCII+or+Hex&quot; name=&quot;Printing+packets+in+ASCII+or+Hex&quot;&gt;Printing packets in ASCII or Hex&lt;/h2&gt;
&lt;p&gt;Print each packet (minus its link level header) in ASCII with &lt;code&gt;-A&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Handy for capturing web pages. Also note that &lt;code&gt;-x&lt;/code&gt; can be used for ASCII and hex characters.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;tcpdump -A -vv -i eno1
tcpdump -x -vv -i eno1&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Same as -X, but also shows the ethernet header.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;tcpdump -XX&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Quieter&quot; name=&quot;Quieter&quot;&gt;Quieter&lt;/h2&gt;
&lt;p&gt;Using -q supresses some protocol information, -t supresses timestamps.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;tcpdump -q -i eth0
tcpdump -t -i eth0
tcpdump -A -n -q -i eth0 &#039;port 80&#039;
tcpdump -A -n -q -t -i eth0 &#039;port 80&#039;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ref: &lt;a href=&quot;https://github.com/tcpdump-examples/how-to-use-tcpdump&quot;&gt;https://github.com/tcpdump-examples/how-to-use-tcpdump&lt;/a&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>About JS2Py</title>
<link href="https://www.0ink.net/posts/2025/2025-05-25-js2py.html"></link>
<id>urn:uuid:4bf984e4-dfb0-0aab-5c8c-777897c1a1b5</id>
<updated>2025-05-06T00:00:00+02:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[Js2Py translates JavaScript to Python code. Js2Py is able to translate and execute
virtually any JavaScript code.
Js2Py is written in pure python and does not have any dependencies. Basically an implementation
of JavaScript core in pure python.
While intriguing, I do not see the point to do this.  Going from Python to JavaScript seems
to be more useful.
...]]></summary>
<content type="html">&lt;p&gt;&lt;a href=&quot;https://github.com/PiotrDabkowski/Js2Py&quot;&gt;Js2Py&lt;/a&gt; translates JavaScript to Python code. &lt;a href=&quot;https://github.com/PiotrDabkowski/Js2Py&quot;&gt;Js2Py&lt;/a&gt; is able to translate and execute
virtually any JavaScript code.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/PiotrDabkowski/Js2Py&quot;&gt;Js2Py&lt;/a&gt; is written in pure python and does not have any dependencies. Basically an implementation
of JavaScript core in pure python.&lt;/p&gt;
&lt;p&gt;While intriguing, I do not see the point to do this.  Going from Python to JavaScript seems
to be more useful.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;only&lt;/strong&gt; use case I would have for this is to parse those &lt;a href=&quot;https://en.wikipedia.org/wiki/Proxy_auto-config&quot;&gt;Proxy Auto Configuration&lt;/a&gt;
scripts or PAC files.&lt;/p&gt;
&lt;p&gt;Speaking of Python to JavaScript translators, Google search of &lt;code&gt;py2js&lt;/code&gt; has multiple hits.  The
one that seems more supported and included in the PyPi repository is &lt;a href=&quot;https://github.com/am230/py2js&quot;&gt;am230/py2js&lt;/a&gt;.  Then
again, you are better off using &lt;a href=&quot;/posts/2025/2025-05-06-brython.html&quot;&gt;Brython&lt;/a&gt;.&lt;/p&gt;</content>
</entry>
<entry>
<title>How SanDisk&#039;s ranges compare</title>
<link href="https://www.0ink.net/posts/2025/2025-05-20-sd-classes.html"></link>
<id>urn:uuid:8985bcf5-cbc9-937e-62b9-18227c62bd38</id>
<updated>2025-05-22T00:00:00+02:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[This is not a SanDisk advertisement!


...]]></summary>
<content type="html">&lt;p&gt;This is &lt;em&gt;not&lt;/em&gt; a SanDisk advertisement!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/sd/img1.png&quot; alt=&quot;SanDisk classes 1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/sd/img2.png&quot; alt=&quot;SanDisk classes 2&quot; /&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>Using hooks in git</title>
<link href="https://www.0ink.net/posts/2025/2025-05-15-git-hooks.html"></link>
<id>urn:uuid:365063dc-e19e-2a7d-f2f8-d23ffee4aa50</id>
<updated>2024-11-22T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[

Add additional files to commit
List files that will be committed
Making hooks available

...]]></summary>
<content type="html">&lt;p&gt;&lt;img src=&quot;/images/2025/git.png&quot; alt=&quot;git logo&quot; /&gt;&lt;/p&gt;
&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Add+additional+files+to+commit&quot;&gt;Add additional files to commit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#List+files+that+will+be+committed&quot;&gt;List files that will be committed&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Making+hooks+available&quot;&gt;Making hooks available&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;Here are some tips for using &lt;a href=&quot;https://githooks.com/&quot;&gt;hooks&lt;/a&gt; in &lt;a href=&quot;https://git-scm.com/&quot;&gt;git&lt;/a&gt;.  &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/githook.png&quot; alt=&quot;git hook&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Like many other Version Control Systems, &lt;a href=&quot;https://git-scm.com/&quot;&gt;Git&lt;/a&gt; has a way to fire off custom scripts when
certain important actions occur. There are two groups of these hooks: client-side and
server-side. Client-side hooks are triggered by operations such as committing and merging,
while server-side hooks run on network operations such as receiving pushed commits. You
can use these hooks for all sorts of reasons.  Typically these are used to customize
&lt;a href=&quot;https://git-scm.com/&quot;&gt;Git&lt;/a&gt; for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;automate tasks&lt;/li&gt;
&lt;li&gt;enforce policies through workflows&lt;/li&gt;
&lt;li&gt;ensuring code quality&lt;/li&gt;
&lt;li&gt;running test automation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/githooks.jpg&quot; alt=&quot;git hooks&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;Add+additional+files+to+commit&quot; name=&quot;Add+additional+files+to+commit&quot;&gt;Add additional files to commit&lt;/h2&gt;
&lt;p&gt;Take some files and create items that will be added to commit.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create script to generate meta data&lt;/li&gt;
&lt;li&gt;Create pre-commit hook
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/sh
python generate_commit_artifacts.py
git add path/to/generated/artifacts&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Make hook executable&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The use case for this is to include in the source code repository files
that require specialized tools to generate.&lt;/p&gt;
&lt;h2 id=&quot;List+files+that+will+be+committed&quot; name=&quot;List+files+that+will+be+committed&quot;&gt;List files that will be committed&lt;/h2&gt;
&lt;p&gt;In a pre-commit hook, you can use &lt;code&gt;git diff&lt;/code&gt; to find the files that are staged for commit.
The &lt;code&gt;git diff --cached&lt;/code&gt; command shows the changes between the index (staging area) and your
last commit, effectively listing the files that will be included in the next commit.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/sh
# Pre-commit hook script to find staged files

# Get the list of staged files
staged_files=$(git diff --cached --name-only --diff-filter=ACMR)

# Print the staged files
echo &quot;Staged files:&quot;
echo &quot;$staged_files&quot;

# Run your script for each staged file
for file in $staged_files; do
    # Add your metadata generation script here
    echo &quot;Processing $file&quot;
    # Example: python generate_metadata.py $file
done&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this script:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;git diff --cached --name-only --diff-filter=ACMR&lt;/code&gt; lists the names of the files that are
staged to be committed. The &lt;code&gt;--diff-filter=ACMR&lt;/code&gt; option filters the results to include
only Added &lt;strong&gt;(A)&lt;/strong&gt;, Copied &lt;strong&gt;(C)&lt;/strong&gt;, Modified &lt;strong&gt;(M)&lt;/strong&gt;, and Renamed &lt;strong&gt;(R)&lt;/strong&gt; files.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;staged_files&lt;/code&gt; stores the list of these files.&lt;/li&gt;
&lt;li&gt;The script then loops through each staged file and performs any desired actions,
such as running a metadata generation script.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By using this approach, you can ensure that your pre-commit hook processes only the
files that are actually staged for the next commit. This helps in generating and
committing relevant metadata efficiently. &lt;/p&gt;
&lt;h2 id=&quot;Making+hooks+available&quot; name=&quot;Making+hooks+available&quot;&gt;Making hooks available&lt;/h2&gt;
&lt;p&gt;To ensure that your hooks are available when someone forks your repository, you can
include the hook script within your repository itself and provide instructions on how
to set it up. Here&#039;s a recommended approach:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Create a Directory for Hooks:&lt;/strong&gt; Inside your repository, create a directory named
something like &lt;code&gt;githooks&lt;/code&gt; where you can store your hook scripts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Store your Hook script:&lt;/strong&gt; Place your hook scripts in this directory, make sure
they are executable.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Updated Hooks:&lt;/strong&gt; In the root directory of your repository create or update
a setup script (e.g., &lt;code&gt;setup-hooks.sh&lt;/code&gt;) that will install your hooks in the
appropriate &lt;code&gt;.git/hooks&lt;/code&gt; directory.
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/sh
#
# setup-hooks.sh
#
cd &quot;$(dirname &quot;$0&quot;)&quot; || exit 1
ls -1 githooks | while read hook
do
  [ -x &quot;githooks/$hook&quot; ] || continue
  ln -sf ../../githooks/&quot;$hook&quot; .git/hooks/$hook
done&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Include Setup Instructions:&lt;/strong&gt; Add a section in your &lt;code&gt;README.md&lt;/code&gt; or create a
separate &lt;code&gt;CONTRIBUTING.md&lt;/code&gt; file to include instructions for setting up the Git
hooks:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;## Setting Up Git Hooks

To set up the Git hooks for this repository, run the following command:

  sh setup-hooks.sh&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;By following this approach, anyone who forks your repository can easily set up
the repository hooks by running the setup script. This keeps the process simple and
ensures that everyone working with the repository has the necessary hooks in place.&lt;/p&gt;</content>
</entry>
<entry>
<title>Switching to Giscus</title>
<link href="https://www.0ink.net/posts/2025/2025-05-08-giscus.html"></link>
<id>urn:uuid:14c3ed5f-e12c-c608-0b9d-683ab013a7dc</id>
<updated>2025-05-08T00:00:00+02:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[So since last year I have been using utterances to add comments to my BLOG.  After
reading this post from Max Brenner, I decided to switch to giscus.
Giscus is a comments system powered by GitHub Discussions. It lets visitors leave
comments and reactions on your website via GitHub!
I agree with the comments from Mr. Brenner:

...]]></summary>
<content type="html">&lt;p&gt;So since last year I have been using &lt;a href=&quot;https://utteranc.es/&quot;&gt;utterances&lt;/a&gt; to add comments to my BLOG.  After
reading &lt;a href=&quot;https://shipit.dev/posts/from-utterances-to-giscus.html&quot;&gt;this post&lt;/a&gt; from &lt;a href=&quot;https://shipit.dev/&quot;&gt;Max Brenner&lt;/a&gt;, I decided to switch to &lt;a href=&quot;https://github.com/giscus/giscus&quot;&gt;giscus&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/giscus/giscus&quot;&gt;Giscus&lt;/a&gt; is a comments system powered by &lt;a href=&quot;https://docs.github.com/en/discussions&quot;&gt;GitHub Discussions&lt;/a&gt;. It lets visitors leave
comments and reactions on your website via GitHub!&lt;/p&gt;
&lt;p&gt;I agree with the comments from &lt;a href=&quot;https://shipit.dev/&quot;&gt;Mr. Brenner&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Post reactions &lt;br /&gt;While &lt;a href=&quot;https://utteranc.es/&quot;&gt;utterances&lt;/a&gt; allows to add reactions to comments, &lt;a href=&quot;https://github.com/giscus/giscus&quot;&gt;giscus&lt;/a&gt; also adds reactions
to the post itself.&lt;/li&gt;
&lt;li&gt;Lack of maintenance &lt;br /&gt;Apparently, &lt;a href=&quot;https://utteranc.es/&quot;&gt;utterances&lt;/a&gt; hasn&#039;t been updated in three years and has a long issue list
which is not really being worked upon.&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Brython</title>
<link href="https://www.0ink.net/posts/2025/2025-05-06-brython.html"></link>
<id>urn:uuid:ace6567d-7ee3-51fd-415f-002ae6f62de2</id>
<updated>2025-05-08T00:00:00+02:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[Brython (Browser Python) is an implementation of Python 3 running in the browser,
with an interface to the DOM elements and events.
In a way it is similar to pyjs, however, looks like development has stalled.
Brython is designed to replace Javascript as the scripting language for the Web. As
such, it is a Python 3 implementation (you can take it for a test drive through a web console),
adapted to the HTML5 environment, that is to say with an interface to the DOM objects and events.
...]]></summary>
<content type="html">&lt;p&gt;&lt;a href=&quot;https://brython.info/index.html&quot;&gt;Brython&lt;/a&gt; (Browser Python) is an implementation of Python 3 running in the browser,
with an interface to the DOM elements and events.&lt;/p&gt;
&lt;p&gt;In a way it is similar to &lt;a href=&quot;http://pyjs.org/&quot;&gt;pyjs&lt;/a&gt;, however, looks like development has stalled.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://brython.info/index.html&quot;&gt;Brython&lt;/a&gt; is designed to replace Javascript as the scripting language for the Web. As
such, it is a Python 3 implementation (you can take it for a test drive through a web console),
adapted to the HTML5 environment, that is to say with an interface to the DOM objects and events.&lt;/p&gt;
&lt;p&gt;The demos seem to be impressive enough.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://brython.info/index.html&quot;&gt;Brython&lt;/a&gt; development is hosted in &lt;a href=&quot;https://github.com/brython-dev/brython&quot;&gt;github&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://brython.info/index.html&quot;&gt;Brython&lt;/a&gt; supports the syntax of Python 3, and the modules of the CPython distribution
written in Python, except for the features that are not relevant in the browser context
(writing on disk for instance).&lt;/p&gt;
&lt;p&gt;It includes libraries to interact with DOM elements and events, and with existing Javascript
libraries such as jQuery, D3, Highcharts, Raphael etc. It supports the latest specs of
HTML5/CSS3, and can use CSS Frameworks like Bootstrap3, LESS, SASS etc.&lt;/p&gt;
&lt;p&gt;A tutorial can be found in &lt;a href=&quot;https://realpython.com/brython-python-in-browser/&quot;&gt;Brython: Python in Your Browser&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While interesting, I think it is better to learn TypeScript and JavaScript directlly as
these are more marketable as Front End devlopment languages.&lt;/p&gt;</content>
</entry>
<entry>
<title>Using LLDP on Linux</title>
<link href="https://www.0ink.net/posts/2025/2025-05-01-discovery-protocols.html"></link>
<id>urn:uuid:724ddd67-94f0-cac3-84b7-8fcfe121861b</id>
<updated>2024-11-23T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Introduction
Enabling LLDP on Linux

Using open-lldp
Using lldpd
...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Introduction&quot;&gt;Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Enabling+LLDP+on+Linux&quot;&gt;Enabling LLDP on Linux&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Using+open-lldp&quot;&gt;Using open-lldp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Using+lldpd&quot;&gt;Using lldpd&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Hints+and+Tips&quot;&gt;Hints and Tips&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/lldp.png&quot; alt=&quot;icon&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;Introduction&quot; name=&quot;Introduction&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Link Layer Discovery Protocol (LLDP) is a layer 2 neighbor discovery protocol that allows devices
to advertise device information to their directly connected peers/neighbors. It is best practice to
enable LLDP globally to standardize network topology across all devices if you have a multi-vendor
network.&lt;/p&gt;
&lt;p&gt;Commonly used layer 2 discovery protocols are often vendor-proprietary, for instance, Cisco’s CDP,
Foundry’s FDP, Extreme’s EDP and Nortel’s SONMP. This makes layer 2 discovery difficult in a
heterogeneous environment. To counter this, IETF has introduced a standard vendor-neutral
configuration exchange protocol -- the LLDP.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/lldp-info.png&quot; alt=&quot;info&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Using LLDP, device information such as chassis identification, port ID, port description,
system name and description, device capability (as router, switch, hub…), IP/MAC address,
etc., are transmitted to the neighboring devices. This information is also stored in local
Management Information Databases (MIBs), and can be queried with the Simple Network
Management Protocol (SNMP). The LLDP-enabled devices have an LLDP agent installed in them,
which sends out advertisements from all physical interfaces either periodically or as changes
occur.&lt;/p&gt;
&lt;h2 id=&quot;Enabling+LLDP+on+Linux&quot; name=&quot;Enabling+LLDP+on+Linux&quot;&gt;Enabling LLDP on Linux&lt;/h2&gt;
&lt;p&gt;On Linux there are two solutions that can be used to provide LLDP support on your systems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/intel/openlldp&quot;&gt;open-lldp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/lldpd/lldpd&quot;&gt;lldpd&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I personally like &lt;a href=&quot;https://github.com/lldpd/lldpd&quot;&gt;lldpd&lt;/a&gt; as you only need to install it and it will start working.
Another benefit of &lt;a href=&quot;https://github.com/lldpd/lldpd&quot;&gt;lldpd&lt;/a&gt; is that it has multiple protocol support. &lt;a href=&quot;https://github.com/lldpd/lldpd&quot;&gt;lldpd&lt;/a&gt;
supports CDP, FDP, EDP and SONMP.  Unfortunately, &lt;a href=&quot;https://github.com/lldpd/lldpd&quot;&gt;lldpd&lt;/a&gt; is only available in
&lt;a href=&quot;https://alpinelinux.org/&quot;&gt;Alpine Linux&lt;/a&gt;&#039;s community repositories.  The main repositories only
contain &lt;a href=&quot;https://github.com/intel/openlldp&quot;&gt;open-lldp&lt;/a&gt;.  See &lt;a href=&quot;https://wiki.alpinelinux.org/wiki/Repositories&quot;&gt;this article&lt;/a&gt; for explanation of
the Alpine Linux repositories.&lt;/p&gt;
&lt;p&gt;Because I usually do not like to enable community repos, I had to figure out how
to use &lt;a href=&quot;https://github.com/intel/openlldp&quot;&gt;open-lldp&lt;/a&gt;.  I found an article which explains
how to use &lt;a href=&quot;https://github.com/intel/openlldp&quot;&gt;open-lldp&lt;/a&gt; &lt;a href=&quot;https://wiki.polaire.nl/doku.php?id=lldpad_-_link_layer_discovery_protocol_lldp_agent_daemon&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;Using+open-lldp&quot; name=&quot;Using+open-lldp&quot;&gt;Using open-lldp&lt;/h3&gt;
&lt;p&gt;Install and enable the agent:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;apk add open-lldp
rc-update add lldpad
service lldpad start&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once running, you need to configure the agent.  Currently I am using a
start-up script, but the agent supports a configuration file in
&lt;code&gt;/etc/lldpad.conf&lt;/code&gt;.  Note the config file is updated automatically
by the &lt;code&gt;lldpad&lt;/code&gt; agent when you change its configuration.&lt;/p&gt;
&lt;p&gt;Configure things:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;lldptool -L -i eth0 adminStatus
lldptool -i eth0 -T -V portDesc enableTx=yes
lldptool -i eth0 -T -V sysName  enableTx=yes
lldptool -i eth0 -T -V sysDesc enableTx=yes
lldptool -i eth0 -T -V sysCap enableTx=yes
lldptool -i eth0 -T -V mngAddr ipv4=192.168.42.66 enableTx=yes&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To query neighbors:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;# lldptool -t -n -i eth0

Chassis ID TLV
        MAC: 9c:8e:99:bd:b1:20
Port ID TLV
        Local: 12
Time to Live TLV
        120
Port Description TLV
        Port #12
System Name TLV
        switch1
System Description TLV
         HP ProCurve 1810G - 24 GE, P.2.12, eCos-2.0, CFE-2.1
System Capabilities TLV
        System capabilities:  Bridge
        Enabled capabilities: Bridge
Management Address TLV
        IPv4: 192.168.40.11
        Ifindex: 25
End of LLDPDU TLV&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;View from switch Web GUI:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/screenshot_lldp.png&quot; alt=&quot;screenshot&quot; /&gt;&lt;/p&gt;
&lt;p&gt;To get configured TLV values:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# lldptool -l -i ne0 adminStatus -c
adminStatus=rxtx&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# lldptool -i ne0 -t -V portDesc -c
enableTx=yes&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# lldptool -i ne0 -t -V mngAddr -c
ipv4=192.168.2.6
ipv6=
enableTx=yes&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Using+lldpd&quot; name=&quot;Using+lldpd&quot;&gt;Using lldpd&lt;/h3&gt;
&lt;p&gt;Enable the community repo in &lt;code&gt;/etc/apk/repositories&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Install and enable the daemon:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;apk add lldpd
rc-update add lldpd
service lldpd start&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is enough by default.  If you want to enable additional
protocols you must modify the file &lt;code&gt;/etc/conf.d/lldpd&lt;/code&gt; and
add the relevant options for additional protocol support: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;-c : Enable the support of CDP protocol. (Cisco)&lt;/li&gt;
&lt;li&gt;-e : Enable the support of EDP protocol. (Extreme)&lt;/li&gt;
&lt;li&gt;-f : Enable the support of FDP protocol. (Foundry)&lt;/li&gt;
&lt;li&gt;-s : Enable the support of SONMP protocol. (Nortel)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Query neighbors:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# lldpctl
-------------------------------------------------------------------------------
LLDP neighbors:
-------------------------------------------------------------------------------
Interface:    eth0, via: LLDP, RID: 1, Time: 0 day, 09:50:19
  Chassis:     
    ChassisID:    mac d4:01:c3:2f:cc:2e
    SysName:      swap4
    SysDescr:     MikroTik RouterOS 6.49.15 (stable) RBD52G-5HacD2HnD
    MgmtIP:       192.168.2.204
    MgmtIface:    4
    MgmtIP:       192.168.2.3
    MgmtIface:    4
    Capability:   Bridge, on
    Capability:   Router, on
    Capability:   Wlan, on
  Port:        
    PortID:       ifname bridge/ether2
    TTL:          120
-------------------------------------------------------------------------------
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Hints+and+Tips&quot; name=&quot;Hints+and+Tips&quot;&gt;Hints and Tips&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/l2net.png&quot; alt=&quot;l2net&quot; /&gt;&lt;/p&gt;
&lt;p&gt;When I first tried this I did a mistake of creating a loop on my network, so STP
disabled the port I was running LLDP.  Weirdly enough, I would occasionally see
neighbors on that port.  So to troubleshoot, I was using &lt;a href=&quot;https://www.tcpdump.org/&quot;&gt;tcpdump&lt;/a&gt;.  These
are some useful options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;-i &lt;em&gt;nic&lt;/em&gt; : Select the interface&lt;/li&gt;
&lt;li&gt;-v or -vv : more protocol decoding&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;-e : Show Ethernet Frame fields&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ether proto 0x88cc&lt;/code&gt; : select LLDP frames (Protocol ID 0x88cc)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Also when configuring &lt;a href=&quot;https://github.com/intel/openlldp&quot;&gt;open-lldp&lt;/a&gt;, I would want to configure on
the physical devices.  I use this loop in &lt;code&gt;bash&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;for nic in $(ls -1 /sys/class/net/)
do
  [ ! -d /sys/class/net/&quot;$nic&quot;/device ] &amp;amp;&amp;amp; continue
  if [ -f /sys/class/net/&quot;$nic&quot;/device/modalias ] ; then
    # Special rule to filter out xen backend interfaces
    grep -q xen-backend /sys/class/net/&quot;$nic&quot;/device/modalias &amp;amp;&amp;amp; continue
  fi

  # CONFIG COMMANDS GO HERE
done&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Normally, LLDP frames will not propagate through Linux bridges.  This is
the intended bridge behaviour.  If you are for example using virtualization
on your system and would like the VMs to see LLDP frames, you can force
LLDP frames to be forwarded.  See &lt;a href=&quot;https://alwaystinkering.wordpress.com/2020/12/30/lldp-traffic-and-linux-bridges/&quot;&gt;this article&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Enable LLDP forwarding:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;echo 0x4000 &amp;gt; /sys/class/net/&amp;lt;bridge_name&amp;gt;/bridge/group_fwd_mask&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a runtime change to make this more permanent, I do it from
the &lt;a href=&quot;https://alpinelinux.org/&quot;&gt;Alpine Linux&lt;/a&gt; &lt;code&gt;/etc/network/interfaces&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;# /etc/network/interfaces

auto br1
iface br1 inet dhcp
  bridge-ports eth0 eth1
  up echo 0x4000 &amp;gt; /sys/class/net/$IFACE/bridge/group_fwd_mask&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On the other hand, on the MikroTik RouterOS switch I am using LLDP
frames are passed through by default.  So on the neighbors list
I would see not just the switch but all the other devices
on the other side of the switch.  The switch has routing, bridging
and switch capabilities.  In RouterOS, bridging is done by
software, while switching is done by a hardware chip on the
device.  Since obviously you would want to use switching when
possible, you can add a switch rule to filter out LLDP frames:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/interface/ethernet/switch/rule&amp;gt; add switch=switch1 ports=ether3,ether4 mac-protocol=lldp copy-to-cpu=no redirect-to-cpu=yes mirror=no
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This needs to be done in the CLI as the WebFIG UI won&#039;t let you select the LLDP protocol.
See &lt;a href=&quot;https://forum.mikrotik.com/viewtopic.php?p=911066#p910863&quot;&gt;this&lt;/a&gt;.  The &lt;code&gt;redirect-to-cpu&lt;/code&gt; make sure the CPU gets incoming LLDP for book keeping
but restricts forwarding.&lt;/p&gt;</content>
</entry>
<entry>
<title>Using 7z for benchmarks</title>
<link href="https://www.0ink.net/posts/2025/2025-04-15-7z-benchmark.html"></link>
<id>urn:uuid:df485417-ea00-3ef8-5a5c-a486b7153cff</id>
<updated>2024-11-22T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Syntax
LZMA benchmark details
LZMA benchmark in multithreading mode
7-Zip benchmark
Examples
...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Syntax&quot;&gt;Syntax&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#LZMA+benchmark+details&quot;&gt;LZMA benchmark details&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#LZMA+benchmark+in+multithreading+mode&quot;&gt;LZMA benchmark in multithreading mode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#7-Zip+benchmark&quot;&gt;7-Zip benchmark&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Examples&quot;&gt;Examples&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#References&quot;&gt;References&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/7-Zip_Logo.png&quot; alt=&quot;7z&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://7-zip.org/&quot;&gt;7zip&lt;/a&gt; comes with a &lt;code&gt;b&lt;/code&gt; (Benchmark) command. &lt;a href=&quot;https://7-zip.org/&quot;&gt;7zip&lt;/a&gt; usually is packaged as
&lt;code&gt;p7z&lt;/code&gt; or similar...&lt;/p&gt;
&lt;p&gt;This command measures speed of the CPU.  Its execution also can be used to check RAM for errors.&lt;/p&gt;
&lt;h2 id=&quot;Syntax&quot; name=&quot;Syntax&quot;&gt;Syntax&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;b [number_of_iterations] [-mmt{N}] [-md{N}] [-mm={Method}]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-md{N}&lt;/code&gt; : change the upper dictionary size to increase memory usage.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-mmt{N}&lt;/code&gt; : n change the number of threads.  Default will use all available cores.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-mm=*&lt;/code&gt; : run complex 7-Zip benchmark.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The LZMA benchmark is default benchmark for benchmark command.&lt;/p&gt;
&lt;p&gt;There are two tests for LZMA benchmark:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Compressing with LZMA method&lt;/li&gt;
&lt;li&gt;Decompressing with LZMA method&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ 7z b

7-Zip (z) 24.08 (x64) : Copyright (c) 1999-2024 Igor Pavlov : 2024-08-11
 64-bit locale=en_US.UTF-8 Threads:16 OPEN_MAX:1024

Compiler:  ver:13.2.0 GCC 13.2.0 : SSE2
Linux : 6.6.56_2 : #1 SMP PREEMPT_DYNAMIC Tue Oct 15 02:54:10 UTC 2024 : x86_64
PageSize:4KB THP:madvise hwcap:178BFBFF hwcap2:2
AMD Ryzen 7 5800U with Radeon Graphics
(A50F00) 

1T CPU Freq (MHz):  4361  4408  4409  4421  4422  4421  4417
8T CPU Freq (MHz): 796% 4246   797% 4175  
16T CPU Freq (MHz): 1419% 3623   1569% 3987  

RAM size:   15412 MB,  # CPU hardware threads:  16
RAM usage:   3559 MB,  # Benchmark threads:     16

                       Compressing  |                  Decompressing
Dict     Speed Usage    R/U Rating  |      Speed Usage    R/U Rating
         KiB/s     %   MIPS   MIPS  |      KiB/s     %   MIPS   MIPS

22:      41524  1462   2763  40395  |     633594  1547   3491  54026
23:      36519  1426   2609  37209  |     596376  1531   3370  51591
24:      34094  1404   2612  36658  |     595727  1556   3360  52270
25:      32470  1379   2689  37074  |     578205  1547   3326  51443
----------------------------------  | ------------------------------
Avr:     36152  1418   2668  37834  |     600975  1545   3387  52333
Tot:            1481   3028  45083
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The LZMA benchmark shows a rating in MIPS (million instructions per second). The rating value
is calculated from the measured speed, and it is normalized with results of Intel Core 2 CPU
with multi-threading option switched off. So if you have modern CPU from Intel or AMD, rating
values in single-thread mode must be close to real CPU frequency.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Dict&lt;/strong&gt; column shows dictionary size. For example, 21 means 2^21 = 2 MB.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Usage&lt;/strong&gt; column shows the percentage of time the processor is working. It&#039;s normalized for a
one-thread load. For example, 180% CPU Usage for 2 threads can mean that average CPU usage is
about 90% for each thread.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;R / U&lt;/strong&gt; column shows the rating normalized for 100% of CPU usage. That column shows
the performance of one average CPU thread.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Avr&lt;/strong&gt; shows averages for different dictionary sizes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tot&lt;/strong&gt; shows averages of the compression and decompression ratings.&lt;/p&gt;
&lt;p&gt;The test data that is used for compression in that test is produced with special algorithm,
that creates data stream that has some properties of real data, like text or execution code.
Note that the speed of LZMA for real data can be slightly different.&lt;/p&gt;
&lt;h2 id=&quot;LZMA+benchmark+details&quot; name=&quot;LZMA+benchmark+details&quot;&gt;LZMA benchmark details&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Compression speed&lt;/strong&gt; strongly depends from memory (RAM) latency, Data Cache size/speed
and TLB. Out-of-Order execution feature of CPU is also important for that test.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Decompression speed&lt;/strong&gt; strongly depends on CPU integer operations. The most important things
for that test are: branch misprediction penalty (the length of pipeline) and the latencies of
32-bit instructions (&amp;quot;multiply&amp;quot;, &amp;quot;shift&amp;quot;, &amp;quot;add&amp;quot; and other). The decompression test has very
high number of unpredictable branches. Note that some CPU architectures (for example, 32-bit
ARM) support instructions that can be conditionally executed. So such CPUs can work without
branches (and without pipeline flushing) in many cases in LZMA decompression code. And such
CPUs can have some speed advantages over other architectures that don&#039;t support complex
conditionally execution. Out-of-Order execution capability is not so important for LZMA
Decompression.&lt;/p&gt;
&lt;p&gt;The test code doesn&#039;t use FPU and SSE. Most of the code is 32-bit integer code. Only some
minor part in compression code uses also 64-bit integers. RAM and Cache bandwidth are not
so important for these tests. The &lt;em&gt;latencies are much more important&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The CPU&#039;s IPC (Instructions per cycle) rate is not very high for these tests. The estimated
value of test&#039;s IPC is 1 (one instruction per cycle) for modern CPU. The compression test
has big number of random accesses to RAM and Data Cache. So big part of execution time the
CPU waits the data from Data Cache or from RAM. The decompression test has big number of
pipeline flushes after mispredicted branches. Such low IPC means that there are some
unloaded CPU resources. But the CPU with Hyper-Threading feature can load these CPU
resources using two threads. So Hyper-Threading provides pretty big improvement in these tests.&lt;/p&gt;
&lt;h2 id=&quot;LZMA+benchmark+in+multithreading+mode&quot; name=&quot;LZMA+benchmark+in+multithreading+mode&quot;&gt;LZMA benchmark in multithreading mode&lt;/h2&gt;
&lt;p&gt;When you specify (N * 2) threads for test, the program creates N copies of LZMA encoder,
and each LZMA encoder instance compresses separated block of test data. Each LZMA encoder
instance creates 3 unsymmetrical execution threads: two big threads and one small thread.
The total CPU load for these 3 threads can vary from 140% to 200%. To provide better CPU
load during compression, you can test the mode, where the number of benchmark threads is
larger than the number of hardware threads.&lt;/p&gt;
&lt;p&gt;Each LZMA encoder instance in multithreading mode divides the task of compression into 3
different tasks, where each task is executed in separated thread. Each of these tasks is
simpler than original task, and it uses less memory. So each thread uses the data cache
and TLB more effectively in multithreading mode. And LZMA encoder is slightly more effective
in multithreading mode in value of &amp;quot;the Speed&amp;quot; divided to &amp;quot;CPU usage&amp;quot;.&lt;/p&gt;
&lt;p&gt;Note that there is some data traffic between 3 threads of LZMA encoder. So data exchange
bandwidth via memory between CPU threads is also can be important, especially in multi-core
system with big number of cores or CPUs.&lt;/p&gt;
&lt;p&gt;All LZMA decoder threads are symmetrical and independent. So the decompression test uses all
hardware threads, if the number of hardware threads is used.&lt;/p&gt;
&lt;h2 id=&quot;7-Zip+benchmark&quot; name=&quot;7-Zip+benchmark&quot;&gt;7-Zip benchmark&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/benchmark.png&quot; alt=&quot;icon&quot; /&gt;&lt;/p&gt;
&lt;p&gt;With &lt;code&gt;-mm=*&lt;/code&gt; switch you can run a complex benchmark for 7-Zip code. It tests hash calculation
methods, compression and encryption codecs of 7-Zip. Note that the tests of LZMA have big
weight in &amp;quot;total&amp;quot; results. And the results are normalized with AMD K8 cpu in that complex
benchmark.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ 7z b -mm=*

7-Zip (z) 24.08 (x64) : Copyright (c) 1999-2024 Igor Pavlov : 2024-08-11
 64-bit locale=en_US.UTF-8 Threads:16 OPEN_MAX:1024

 m=*
Compiler:  ver:13.2.0 GCC 13.2.0 : SSE2
Linux : 6.6.56_2 : #1 SMP PREEMPT_DYNAMIC Tue Oct 15 02:54:10 UTC 2024 : x86_64
PageSize:4KB THP:madvise hwcap:178BFBFF hwcap2:2
AMD Ryzen 7 5800U with Radeon Graphics
(A50F00) 

1T CPU Freq (MHz):  4404  4313  4421  4426  4405  4422  4415
8T CPU Freq (MHz): 799% 4259   797% 4175  
16T CPU Freq (MHz): 1434% 3653   1573% 4002  

RAM size:   15412 MB,  # CPU hardware threads:  16
RAM usage:   3647 MB,  # Benchmark threads:     16

Method           Speed Usage    R/U Rating   E/U Effec
                 KiB/s     %   MIPS   MIPS     %     %

CPU                     1564   4061  63537
CPU                     1574   3944  62086
CPU                     1560   3921  61174   103  1600

LZMA:x1          81225  1491   2009  29943    53   783
                642809  1554   3308  51409    87  1345
LZMA:x3          39193  1442   1670  24080    44   630
                581692  1499   3255  48803    85  1276
LZMA:x5:mt1      29052  1489   2437  36294    64   949
                582429  1523   3223  49106    84  1284
LZMA:x5:mt2      30660  1507   2541  38304    66  1002
                580898  1531   3199  48977    84  1281
Deflate:x1      559275  1548   4589  71015   120  1857
               2071309  1557   4132  64349   108  1683
Deflate:x5      174091  1484   4516  67029   118  1753
               2045737  1532   4144  63501   108  1661
Deflate:x7       64640  1542   4646  71619   122  1873
               2071119  1548   4152  64262   109  1681
Deflate64:x5    157003  1505   4507  67846   118  1774
               2056814  1528   4209  64326   110  1682
BZip2:x1         82001  1549   3198  49542    84  1296
                517893  1510   3718  56136    97  1468
BZip2:x5         36407  1441   2108  30384    55   795
                197521  1504   2577  38765    67  1014
BZip2:x5:mt2     32927  1515   1814  27480    47   719
                191315  1510   2486  37547    65   982
BZip2:x7         17540  1518   2994  45442    78  1189
                195467  1506   2545  38329    67  1002
PPMD:x1          58411  1552   3894  60412   102  1580
                 46787  1551   3552  55097    93  1441
PPMD:x5          31269  1520   3486  52994    91  1386
                 26949  1511   3342  50501    87  1321
Swap4        411409038  1525   1726  26330    45   689
             407703538  1531   1704  26093    45   682
Delta:4       19134561  1550   3794  58781    99  1537
              11982527  1545   3177  49080    83  1284
BCJ           21499405  1554   2833  44031    74  1152
              21507528  1563   2819  44047    74  1152
ARM64         30825468  1560   2024  31565    53   826
              30076127  1552   1984  30798    52   806
RISCV         20395348  1547   1350  20885    35   546
              16120110  1557   1060  16507    28   432
AES256CBC:1    1482399  1562   2332  36431    61   953
               1370916  1550   2174  33692    57   881
AES256CBC:2   11935671  1526   6407  97777   168  2557
              42505953  1544   2818  43526    74  1138
AES256CBC:3   11619172  1509   6307  95184   165  2490
              74469288  1512   2521  38128    66   997
CRC32:12      24817472  1541   1649  25413    43   665
CRC32:32    
CRC32:64    
CRC64         20803049  1537   1386  21302    36   557
XXH64         84786144  1565   1387  21705    36   568
SHA256:1       1864713  1565   2431  38040    64   995
SHA256:2      20485066  1547   2755  42609    72  1114
SHA1:1         3175442  1567   1897  29722    50   777
SHA1:2        20937576  1546   2644  40870    69  1069
BLAKE2sp:1     3329474  1562   3491  54550    91  1427
BLAKE2sp:2     8505983  1558   2236  34841    58   911
BLAKE2sp:3    16257632  1538   2165  33296    57   871

CPU                     1563   3391  53000
------------------------------------------------------
Tot:                    1518   2916  44317    76  1159
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;strong&gt;CPU&lt;/strong&gt; rows show CPU frequency. It&#039;s measured for sequence of simple CPU instructions.
Note: It can be inaccurate, if hyper-threading is used.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Effec&lt;/strong&gt; column shows Efficiency - the Rating normalized to CPU frequency.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;E / U&lt;/strong&gt; column shows the Efficiency normalized for 100% of CPU usage.&lt;/p&gt;
&lt;h2 id=&quot;Examples&quot; name=&quot;Examples&quot;&gt;Examples&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;run benchmarking
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;7z b&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;run benchmarking with one thread and 64 MB dictionary.
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;7z b -mmt1 -md26&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;run benchmarking for 30 iterations. It can be used to check RAM for errors.
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;7z b 30&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;run complex 7-Zip benchmark.
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;7z b -mm=*&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;run complex 7-Zip benchmark for different number of threads : (1, max/2, max), where
max is number of available hardware threads. So it can test 3 main modes: single-thread,
multi-thread without hyper-threading, multi-thread with hyper-threading. 
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;7z b -mm=* -mmt=*&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;References&quot; name=&quot;References&quot;&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://7-zip.org/&quot;&gt;7-zip home page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://7-zip.opensource.jp/chm/cmdline/commands/bench.htm&quot;&gt;https://7-zip.opensource.jp/chm/cmdline/commands/bench.htm&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Replacing failed drives in a md array</title>
<link href="https://www.0ink.net/posts/2025/2025-04-01-replacing-failed-drives.html"></link>
<id>urn:uuid:166a148d-3005-5106-e031-e2c7bd9545d7</id>
<updated>2025-04-01T00:00:00+02:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
In a Linux softraid, you may have to replace failed drives.  As usual,
backups are still needed, but hardware failures can be covered by
RAID levels.


...]]></summary>
<content type="html">&lt;p&gt;&lt;img src=&quot;/images/2025/brokenhd.png&quot; alt=&quot;Broken HD&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In a Linux softraid, you may have to replace failed drives.  As usual,
backups are still needed, but hardware failures can be covered by
RAID levels.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/burning.png&quot; alt=&quot;burning&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Check the array status with &lt;code&gt;cat /proc/mdstat&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;xm3:~# cat /proc/mdstat
Personalities : [raid1] 
md0 : active raid1 sdb[0] sdc[2]
      67042304 blocks super 1.2 [2/2] [UU]

unused devices: &amp;lt;none&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Check the serial number of your drives to make sure you
are replacing the right one: &lt;code&gt;lsblk -do +VENDOR,MODEL,SERIAL&lt;/code&gt;:
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;NAME  MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS VENDOR MODEL                 SERIAL
loop0   7:0    0 108.1M  1 loop /.modloop                
sda   253:0    0   232G  0 disk             ATA    WDC_WD2500AVVS-73M8B0 WD-WCAV94350152
sdb   253:16   0   232G  0 disk             ATA    WDC_WD2500AVVS-73M8B0 WD-WCAV94350152
sdc   253:32   0   232G  0 disk             ATA    WDC_WD2500AVVS-73M8B0 WD-WCAV94350152&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Remove the faulty drive&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hot plugging, you can do this while the system is running, if your hardware
supports it.
&lt;ul&gt;
&lt;li&gt;Remove the drive from the system.&lt;/li&gt;
&lt;li&gt;Run:
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mdadm --manage /dev/md0 --remove failed&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that &lt;code&gt;failed&lt;/code&gt; worked for me.  Other examples mentioned &lt;code&gt;detached&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Warm plugging, some hardware requires you to enter some commands:
&lt;ul&gt;
&lt;li&gt;mark drive as faulty
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mdadm --manage /dev/md0 --fail /dev/sdc`&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;remove drive from array
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mdadm --manage /dev/md0 --remove /dev/sdc&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;remove the drive from the kernel
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;echo 1 &amp;gt; /sys/block/sdc/device/delete&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This is how mdstat looks like after:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;xm3:~# cat /proc/mdstat 
Personalities : [raid1] 
md0 : active raid1 vdb[0]
      67042304 blocks super 1.2 [2/1] [U_]

 unused devices: &amp;lt;none&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When removing the physical drive, if possible, unplug the power cable
first, data cable next.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Insert the new drive.  When possible, plug the data cable first, power cable next.
Wait 10-15 seconds&lt;/li&gt;
&lt;li&gt;Run
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;for file in /sys/class/scsi_host/*/scan; do
  echo &quot;- - -&quot; &amp;gt; $file;
done;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will re-scan the SATA buses.  This is not always needed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;Add the new drive to the array:
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mdadm --add /dev/md0 /dev/sdc&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Check that the configuration updated correctly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;xm3:~# mdadm --detail /dev/md0
/dev/md0:
           Version : 1.2
     Creation Time : Tue Nov  5 14:44:26 2024
        Raid Level : raid1
        Array Size : 67042304 (63.94 GiB 68.65 GB)
     Used Dev Size : 67042304 (63.94 GiB 68.65 GB)
      Raid Devices : 2
     Total Devices : 2
       Persistence : Superblock is persistent

       Update Time : Wed Nov 13 14:03:42 2024
             State : clean, degraded, recovering 
    Active Devices : 1
   Working Devices : 2
    Failed Devices : 0
     Spare Devices : 1

Consistency Policy : resync

    Rebuild Status : 0% complete

              Name : xm3.virtual:0  (local to host xm3.virtual)
              UUID : 95d17a6b:bee76241:c72a05d1:3cfd7d62
            Events : 28

    Number   Major   Minor   RaidDevice State
       0     253       16        0      active sync   /dev/sdb
       2     253       32        1      spare rebuilding   /dev/sdc&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can monitor the re-build process with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# watch cat /proc/mdstat
Personalities : [raid1] 
md0 : active raid1 vdc[2] vdb[0]
      67042304 blocks super 1.2 [2/1] [U_]
      [=========&amp;gt;...........]  recovery = 45.6% (30607360/67042304) finish=3.0min speed=200060K/sec

unused devices: &amp;lt;none&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You should check the output of &lt;code&gt;dmesg&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;[  703.836264] md/raid1:md0: Disk failure on sdc, disabling device.
[  703.836264] md/raid1:md0: Operation continuing on 1 devices.
[ 1789.815521] md: recovery of RAID array md0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Simlarly, you should use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;smartctl -a /dev/sdc&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To check the health status of the drive.&lt;/p&gt;
&lt;p&gt;In here I am using raw drives (without partitioning).  If you are using partitions
make sure to run &lt;code&gt;fdisk&lt;/code&gt; when appropriate.&lt;/p&gt;
&lt;p&gt;If you have &lt;code&gt;smartmontools&lt;/code&gt; installed and running, we need to reset the daemon so it
doesn&#039;t keep warning about the drive we removed.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/advanced-troubleshooting-lo.png&quot; alt=&quot;advanced troubleshooting&quot; /&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>3D Prints</title>
<link href="https://www.0ink.net/posts/2025/2025-03-17-3dprint.html"></link>
<id>urn:uuid:914e046e-5f86-f328-5f83-785e8abdb78c</id>
<updated>2025-03-16T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[The other day, the 3D model I created back in 2016
finally broke.
So, as such I created an updated model:


For this I am using the same service from my 3D Printing updates
...]]></summary>
<content type="html">&lt;p&gt;The other day, the 3D model I created back in &lt;a href=&quot;/posts/2016/2016-03-20-starting-with-3d-printing.html&quot;&gt;2016&lt;/a&gt;
finally broke.&lt;/p&gt;
&lt;p&gt;So, as such I created an updated model:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/3d-prints/soap-top-lr.jpg&quot; alt=&quot;top view&quot; /&gt;
&lt;img src=&quot;/images/2025/3d-prints/soap-bot-lr.jpg&quot; alt=&quot;bottom view&quot; /&gt;&lt;/p&gt;
&lt;p&gt;For this I am using the same service from my &lt;a href=&quot;/posts/2024/2024-07-15-3d-printing.html&quot;&gt;3D Printing updates&lt;/a&gt;
article.  So far they have been quite reliable.&lt;/p&gt;
&lt;p&gt;This model took only two attempts to get to the working version.  The first attempt
was soooo stupid.  I created the model thinking in &amp;quot;centimeters&amp;quot;, but uploaded
the model using &amp;quot;milimeters&amp;quot; as the unit.  Obviously, the results were tiny.&lt;/p&gt;
&lt;p&gt;The new model, did not use any &lt;a href=&quot;https://www.hubs.com/knowledge-base/supports-3d-printing-technology-overview/&quot;&gt;support material&lt;/a&gt;.  I achieve this by splitting
the model into two sections which later are glued together.  The top section and the bottom
section.&lt;/p&gt;
&lt;p&gt;This also allows the two sections to be printed with different fill densities, which can
reduce the cost of the printing.&lt;/p&gt;
&lt;p&gt;After I did the print, I tweaked the model, so that the bottom is 1mm smaller than the top.
Also the top uses less material.
I did not try printing the tweaked model, but I think it would have a nicer effect
than the original.&lt;/p&gt;
&lt;p&gt;The models can be found here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Printed version in cm (scale by 10x when printing)
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/src/content/images/2025/3d-prints/SoapParts-b-V2.0-in-cm.stl&quot;&gt;Top part&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/src/content/images/2025/3d-prints/SoapParts-a-V2.0-in-cm.stl&quot;&gt;Bottom part&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Tweaked model (scale in mim now)
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/src/content/images/2025/3d-prints/SoapParts-b-V2.1.stl&quot;&gt;Top part&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/src/content/images/2025/3d-prints/SoapParts-a-V2.1.stl&quot;&gt;Bottom part&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Filesystem discard</title>
<link href="https://www.0ink.net/posts/2025/2025-03-15-discard.html"></link>
<id>urn:uuid:a9ec750e-afec-6d48-d3fb-71991778eb65</id>
<updated>2025-03-16T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Now with the prevalence of SSD's for storage, it is important
to make sure that the DISCARD operation is used.  This
is specially true as this can increase the lifetime of your flash
storage by reducing the need to re-map blocks by simply marking
them as freed.
...]]></summary>
<content type="html">&lt;p&gt;&lt;img src=&quot;/images/2025/ssd-lo.png&quot; alt=&quot;ssd&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Now with the prevalence of SSD&#039;s for storage, it is important
to make sure that the &lt;a href=&quot;https://en.wikipedia.org/wiki/Trim_(computing)&quot;&gt;DISCARD&lt;/a&gt; operation is used.  This
is specially true as this can increase the lifetime of your flash
storage by reducing the need to re-map blocks by simply marking
them as freed.&lt;/p&gt;
&lt;p&gt;In Linux, because the flexibility of the block device infrastructure
it is very easy to layer different block device drivers to add
functionality.  The risk of doing that is that &lt;a href=&quot;https://en.wikipedia.org/wiki/Trim_(computing)&quot;&gt;Discard&lt;/a&gt;
capabilities can be left out accidentally.&lt;/p&gt;
&lt;p&gt;To examine if &lt;a href=&quot;https://en.wikipedia.org/wiki/Trim_(computing)&quot;&gt;Discard&lt;/a&gt; is enabled accross the different
layers, you can use the command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;lsblk --discard&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will output something like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;NAME            DISC-ALN DISC-GRAN DISC-MAX DISC-ZERO
loop0                  0        4K       4G         0
sda                    0        4K       2G         0
└─crypt-pool           0        4K       2G         0
  └─pool-imglib        0        4K       2G         0
sdb                    0        0B       0B         0
├─sdb1                 0        0B       0B         0
└─sdb2                 0        0B       0B         0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make sure that the values under column &lt;code&gt;DISC-GRAN&lt;/code&gt; and &lt;code&gt;DISC-MAX&lt;/code&gt; are non-zero.&lt;/p&gt;
&lt;p&gt;Once you have identified that discard is properly configured you can
start discarding using these commands:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Discard on demand, by simply running:
&lt;pre&gt;&lt;code&gt;fstrim --all&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Add the &lt;code&gt;discard&lt;/code&gt; option to the mount command.  This is supported by several
filesystems and will call &lt;a href=&quot;https://en.wikipedia.org/wiki/Trim_(computing)&quot;&gt;discard&lt;/a&gt; as files are deleted.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;fstrim --all&lt;/code&gt; on a cron job either weekly or monthly.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In addition, if you are going to delete a logical volume, you could do:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;blkdiscard DEVICE&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;on the logical volume &lt;strong&gt;before&lt;/strong&gt; running &lt;code&gt;lvremove&lt;/code&gt; to discard the storage
associated to the logical volume.&lt;/p&gt;
&lt;p&gt;As I mentioned earlier, it is important to make sure that all layers have discard
enabled.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Low level disc drivers.  These should detect that the underlying drive is capable
of using &lt;code&gt;discard&lt;/code&gt; automatically.&lt;/li&gt;
&lt;li&gt;mdadm : supported and enabled by default. &lt;a href=&quot;https://serverfault.com/questions/508459/implementing-linux-fstrim-on-ssd-with-software-md-raid&quot;&gt;(1)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;dmcrypt - supported, defaults to &lt;strong&gt;off&lt;/strong&gt; for security, requires explicity enabling. &lt;a href=&quot;https://wiki.gentoo.org/wiki/Dm-crypt&quot;&gt;(2)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;lvm2 : suppored and enabled by default. &lt;a href=&quot;https://wiki.gentoo.org/wiki/SSD#LVM&quot;&gt;(3)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Filesystems:
&lt;ul&gt;
&lt;li&gt;ext3/ext4: supports discard mount option and fstrim&lt;/li&gt;
&lt;li&gt;btrfs: supports discard mount option and fstrim&lt;/li&gt;
&lt;li&gt;f2fs: supports discard mount option and fstrim&lt;/li&gt;
&lt;li&gt;xfs: supports discard mount option and fstrim&lt;/li&gt;
&lt;li&gt;jfs: supports discard mount option and fstrim&lt;/li&gt;
&lt;li&gt;ntfs: supports discard mount option with the ntfs3 driver and fstrim&lt;/li&gt;
&lt;li&gt;vfat: supports discard mount option and fstrim&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Xen VMs using block devices will automatically detect &lt;a href=&quot;https://en.wikipedia.org/wiki/Trim_(computing)&quot;&gt;discard&lt;/a&gt; and enable the interface
for virtual drives.&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>DRBD Preload</title>
<link href="https://www.0ink.net/posts/2025/2025-03-01-drbd-preload.html"></link>
<id>urn:uuid:75c05110-3230-1d77-00ab-7f0afe85d76d</id>
<updated>2024-11-22T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
This is a continuation to VM disk replication.
One of the steps needed for VM disk replication
is the need to pre-load the replicated volume.
For the project I wrote, we had two mechanisms:

...]]></summary>
<content type="html">&lt;p&gt;&lt;img src=&quot;/images/2025/hd.png&quot; alt=&quot;hd&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This is a continuation to &lt;a href=&quot;/posts/2025/2024-11-15-vm-disk-repl.html&quot;&gt;VM disk replication&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;One of the steps needed for &lt;a href=&quot;/posts/2025/2024-11-15-vm-disk-repl.html&quot;&gt;VM disk replication&lt;/a&gt;
is the need to pre-load the replicated volume.&lt;/p&gt;
&lt;p&gt;For the project I wrote, we had two mechanisms:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Copy from an attached virtual disk image&lt;/li&gt;
&lt;li&gt;Download from an image server&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Copying from an attached virtual disk is straightforward use of the &lt;code&gt;dd&lt;/code&gt; command.&lt;/p&gt;
&lt;p&gt;Downloading from an image server was slightly triciker as it needed to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Resume downloads (because the large image sizes)&lt;/li&gt;
&lt;li&gt;Images should be stored as compressed qcow2 files.&lt;/li&gt;
&lt;li&gt;Use gzip on the fly so as to compress large empty disk volume areas.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This was accomplished through a script that would convert qcow2 files into raw:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;
#!/usr/bin/env python3
#
# Stream qemu image as a raw image
#
from argparse import ArgumentParser
import nbd  # Requires python3-libnbd
import sys
import subprocess

def mycli():
  &#039;&#039;&#039;Create an ArgumentParser

  :returns ArgumentParser:
  &#039;&#039;&#039;
  cli = ArgumentParser(
    description = &#039;Stream a qemu image in raw format&#039;
  )
  cli.add_argument(&#039;-c&#039;,&#039;--compress&#039;,dest=&#039;gzip&#039;,help=&#039;Enable gzip compression&#039;, action=&#039;store_true&#039;)
  cli.add_argument(&#039;--blocksize&#039;,help=&#039;Read block size&#039;,type=int, default=32*1024*1024)
  cli.add_argument(&#039;file&#039;,help=&#039;Disk image&#039;)
  cli.add_argument(&#039;offset&#039;,help=&#039;Byte offset to skip&#039;,nargs=&#039;?&#039;,type=int,default=0)
  cli.add_argument(&#039;count&#039;,help=&#039;Byte count to send&#039;,nargs=&#039;?&#039;,type=int)
  return cli

if __name__ == &#039;__main__&#039;:
  cli = mycli()
  args = cli.parse_args()

  n = nbd.NBD()
  cmd = [&#039;qemu-nbd&#039;, &#039;--read-only&#039;, &#039;--persistent&#039;, args.file]
  n.connect_systemd_socket_activation(cmd)

  if args.gzip:
    gzip = subprocess.Popen([&#039;gzip&#039;], stdin=subprocess.PIPE)
    fp = gzip.stdin
  else:
    fp = sys.stdout.buffer

  offset = args.offset
  size = n.get_size()
  if not args.count is None and offset + args.count &amp;lt; size:
    size = offset + args.count

  while offset &amp;lt; size:
    c = min(args.blocksize, size-offset)
    b = n.pread(c, offset)
    fp.write(b)
    offset += c

  n.shutdown()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And some bash script to retrieve the data:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;comma() {
  echo &quot;$1&quot; | sed &#039;:a;s/\B[0-9]\{3\}\&amp;gt;/,&amp;amp;/;ta&#039;                                                              
}

preload_in_guest() {
   [ -z &quot;$preload_url&quot; ] &amp;amp;&amp;amp; return
   ( echo &quot;$preload_url&quot; | grep &#039;^https*://&#039;) || return 0

   # We do this stupid process because we would time out for large
   # downloads
   cksz=$(expr 32 &#039;*&#039; 1024 &#039;*&#039; 1024) # 32M chunks

   imgsz=$(wget -nv -O- &quot;${preload_url}?chonky=size&quot;)
   if ! ( echo &quot;$imgsz&quot; | grep -q &#039;^[0-9][0-9]*$&#039; ) ; then
     echo &quot;Unable to determine image size&quot;
     exit
   fi

   # OK, break it into chunks
   count=$(expr $imgsz / $cksz)
   if [ $(expr $imgsz - $(expr $count \* $cksz)) -gt 0 ] ; then
     count=$(expr $count + 1)
   fi
   echo &quot;WILL DOWNLOAD IMAGE $(comma $imgsz)b IN $(comma $count) CHUNKS&quot;

   offset=0
   seek=0
   while [ $seek -lt $count ]
   do
     echo &quot;Reading chunk: $(comma $seek) of $(comma $count) ( $(expr ${seek}00 / $count)% )&quot;
     if ! (wget -nv -O- &quot;${preload_url}?chonky=$offset,$cksz&quot; \
        | gunzip \
        | dd of=/dev/drbd0 bs=$cksz seek=$seek) ; then
       echo &quot;Error retrieving chunk: $seek&quot;
       exit 1
     fi
     seek=$(expr $seek + 1)
     offset=$(expr $offset + $cksz)
   done
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On the server size, a PHP script is used that gets &lt;code&gt;chonky&lt;/code&gt; from the query parameters and
uses the value formatted as &lt;code&gt;offset,size&lt;/code&gt; to call the python script.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/preload.png&quot; alt=&quot;preload&quot; /&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>VM managed disk replication</title>
<link href="https://www.0ink.net/posts/2025/2025-02-15-vm-disk-repl.html"></link>
<id>urn:uuid:f1e6be8d-50ea-ef43-f719-2e2692cc88f0</id>
<updated>2024-11-15T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Introduction
High level overview
Deployment

Required packages
...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Introduction&quot;&gt;Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#High+level+overview&quot;&gt;High level overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Deployment&quot;&gt;Deployment&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Required+packages&quot;&gt;Required packages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#DRBD+configuration&quot;&gt;DRBD configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Configure+NBD+server&quot;&gt;Configure NBD server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Configure+iSCSI+target&quot;&gt;Configure iSCSI target&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Primary+server&quot;&gt;Primary server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Secondary+server&quot;&gt;Secondary server&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Client+setup&quot;&gt;Client setup&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#KVM+NBD+client&quot;&gt;KVM NBD client&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#KVM+iSCSI+client&quot;&gt;KVM iSCSI client&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Performance+Impact&quot;&gt;Performance Impact&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/repl.png&quot; alt=&quot;repl&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;Introduction&quot; name=&quot;Introduction&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;This recipe is for adding disk replication to VMs using KVM hypervisor.  The idea is to keep
VM operations independant of storage operations in non-vertically integrated support
teams.  In this scenario, VM Operators only need to manage VMs, and storage is delegated to
a storage team that manages virtualized storage delivery services, one of these being
replicated storage service.&lt;/p&gt;
&lt;h2 id=&quot;High+level+overview&quot; name=&quot;High+level+overview&quot;&gt;High level overview&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/hl-erised-lo.png&quot; alt=&quot;Block diagram&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This solution is based on &lt;a href=&quot;https://releases.ubuntu.com/jammy/&quot;&gt;Ubuntu 22.04&lt;/a&gt;.  Note that normal
disk images can not be used as repliacted disks unless they have a &lt;em&gt;DRBD&lt;/em&gt; device
signature already embedded on them.&lt;/p&gt;
&lt;p&gt;There are two KVM hosts, for providing a High Availability cluster.  There are two VMs,
each running on the different KVM hosts.  These mirror VMs are managing the replication.&lt;/p&gt;
&lt;p&gt;There are two additional VMs for running the applicaiton.  One &lt;em&gt;one&lt;/em&gt; of these VMs should
be active.&lt;/p&gt;
&lt;h2 id=&quot;Deployment&quot; name=&quot;Deployment&quot;&gt;Deployment&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Create mirror VMs with the same configuration:
&lt;ul&gt;
&lt;li&gt;2 vCPUs&lt;/li&gt;
&lt;li&gt;4096MB RAM&lt;/li&gt;
&lt;li&gt;Netwoks:
&lt;ul&gt;
&lt;li&gt;1 vNIC for replication (does not need to be routed)&lt;/li&gt;
&lt;li&gt;1 vNIC for iSCSI/NBD protocol (only needs to be routed to clients, i.e. KVM host or
client VMs)&lt;/li&gt;
&lt;li&gt;1 vNIC for management and accessing Ubuntu software repositories.&lt;/li&gt;
&lt;li&gt;1 OS disk, 1 data disk&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Primary (mirror-a):
&lt;ul&gt;
&lt;li&gt;Download required packages from Ubuntu repos&lt;/li&gt;
&lt;li&gt;Configure DRBD and start first sync (this happens in the background)&lt;/li&gt;
&lt;li&gt;Download or copy disk pre-load image&lt;/li&gt;
&lt;li&gt;Set-up NBD server or iSCSI target&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Secondary (mirror-b):
&lt;ul&gt;
&lt;li&gt;Download required packages from Ubuntu repos&lt;/li&gt;
&lt;li&gt;Configure DRBD (as secondary)&lt;/li&gt;
&lt;li&gt;Set-up but &lt;em&gt;NOT&lt;/em&gt; start NBD server or iSCSI target&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Required+packages&quot; name=&quot;Required+packages&quot;&gt;Required packages&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;drbd-utils : Needed for DRBD&lt;/li&gt;
&lt;li&gt;tgt : Needed for iSCSI&lt;/li&gt;
&lt;li&gt;nbd-server : Used for the NBD server&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;DRBD+configuration&quot; name=&quot;DRBD+configuration&quot;&gt;DRBD configuration&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Recommended&lt;/strong&gt;: Modify &lt;code&gt;/etc/hosts&lt;/code&gt; to include IP address of peer VMs.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/drbd.png&quot; alt=&quot;drbd&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Install DRBD drivers:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;apt -y install linux-modules-extra-$(uname -r)
modprobe drbd&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;path: &lt;code&gt;/etc/drbd.conf&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;     global { usage-count no; }
     common { disk { resync-rate 100M; } }
     resource r0 {
        protocol C;
        startup {
                wfc-timeout  15;
                degr-wfc-timeout 60;
        }
        net {
                cram-hmac-alg sha1;
                shared-secret &quot;secret&quot;;
        }
        on &lt;img src=&quot;NacoWiki/web/albatros.php/posts/2025/vmbase&quot; alt=&quot;vmbase&quot; title=&quot;vmbase&quot;&gt;a {
                device /dev/drbd0;
                disk /dev/sdb;
                address ${vm1_ipaddr}:7788;
                meta-disk internal;
        }
        on &lt;img src=&quot;NacoWiki/web/albatros.php/posts/2025/vmbase&quot; alt=&quot;vmbase&quot; title=&quot;vmbase&quot;&gt;b {
                device /dev/drbd0;
                disk /dev/sdb;
                address ${vm2_ipaddr}:7788;
                meta-disk internal;
        }
     } 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Initialize DBRD device:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/sh
#
# Do not initialize it twice!
(blkid /dev/sdb | grep &#039;TYPE=&quot;drbd&quot;&#039;) &amp;amp;&amp;amp; exit 0
. /root/mirdata.txt # defines $drbd_role as primary or secondary
drbdadm create-md r0
systemctl enable drbd.service
systemctl start drbd.service
#
case &quot;$drbd_role&quot; in
primary)
  drbdadm -- --overwrite-data-of-peer primary all
  ;;
esac&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point &lt;code&gt;/dev/drbd0&lt;/code&gt; can be pre-loaded with the contents of the replicated drive.&lt;/p&gt;
&lt;h3 id=&quot;Configure+NBD+server&quot; name=&quot;Configure+NBD+server&quot;&gt;Configure NBD server&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/nas.png&quot; alt=&quot;nas&quot; /&gt;&lt;/p&gt;
&lt;p&gt;path: &lt;code&gt;/etc/nbd-server/config&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;[generic]
[r0]
exportname = /dev/drbd0
# Default to port 10809&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Start primary:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;systemctl enable nbd-server
systemctl start nbd-server&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Start secondary:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;systemctl disable ndb-server
systemctl stop nbd-server       
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Configure+iSCSI+target&quot; name=&quot;Configure+iSCSI+target&quot;&gt;Configure iSCSI target&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/iscsi.png&quot; alt=&quot;iSCSI&quot; /&gt;&lt;/p&gt;
&lt;p&gt;path: /etc/tgt/conf.d/target1.conf&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;# if you set some devices, add &amp;lt;target&amp;gt;-&amp;lt;/target&amp;gt; and set the same way with follows
# naming rule : [ iqn.(year)-(month).(reverse of domain name):(any name you like) ]
&amp;lt;target iqn.2023-10.world.srv:dlp.target01&amp;gt;
  # provided devicce as a iSCSI target
  backing-store /dev/drbd0
  # iSCSI Initiator&#039;s IQN you allow to connect
  # initiator-name iqn.2023-10.world.srv:node01.initiator01
&amp;lt;/target&amp;gt; 
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Primary+server&quot; name=&quot;Primary+server&quot;&gt;Primary server&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;systemctl enable tgt
systemctl restart tgt       
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Secondary+server&quot; name=&quot;Secondary+server&quot;&gt;Secondary server&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;systemctl disable tgt
systemctl stop tgt&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Client+setup&quot; name=&quot;Client+setup&quot;&gt;Client setup&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/nas2.png&quot; alt=&quot;clients&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The mirrored storage is exported either as NBD or iSCSI target.&lt;/p&gt;
&lt;p&gt;VM clients can mount the storage using the facilities available for that VM&#039;s Operating
System.&lt;/p&gt;
&lt;p&gt;For KVM volumes (to use as OS for example) these are configured in the VM XML
depending on the protocol to use.&lt;/p&gt;
&lt;h3 id=&quot;KVM+NBD+client&quot; name=&quot;KVM+NBD+client&quot;&gt;KVM NBD client&lt;/h3&gt;
&lt;p&gt;Firewall configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo ufw allow out 10809/tcp&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;VM XML configuration snippet:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;disk type=&#039;network&#039; device=&#039;disk&#039;&amp;gt;
      &amp;lt;driver name=&#039;qemu&#039; type=&#039;raw&#039; cache=&#039;none&#039;/&amp;gt;
      &amp;lt;source protocol=&#039;nbd&#039; name=&#039;r0&#039; &amp;gt;
        &amp;lt;host name=&#039;ip-address-of-server&#039; port=&#039;10809&#039;/&amp;gt;
      &amp;lt;/source&amp;gt;
      &amp;lt;target dev=‘vdc&#039; bus=‘virtio&#039;/&amp;gt;
&amp;lt;/disk&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Port 10809 is the default, so that setting can be omitted.&lt;/p&gt;
&lt;h3 id=&quot;KVM+iSCSI+client&quot; name=&quot;KVM+iSCSI+client&quot;&gt;KVM iSCSI client&lt;/h3&gt;
&lt;p&gt;For simplicity we are not using iSCSI authentication and simply uses the iqn identifiers
and potentially IP addresses to control access.&lt;/p&gt;
&lt;p&gt;Firewall Configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo ufw allow out 3260/tcp&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;VM XML Configuration snippets:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;disk type=&#039;network&#039; device=&#039;disk&#039;&amp;gt;
    &amp;lt;driver name=&#039;qemu&#039; type=&#039;raw&#039;/&amp;gt;
    &amp;lt;source protocol=&#039;iscsi&#039; name=&#039;iqn.2023-10.world.srv:dlp.target01/1&#039;&amp;gt;
      &amp;lt;host name=&#039;192.168.39.184&#039; port=&#039;3260&#039;/&amp;gt;
      &amp;lt;initiator&amp;gt;
        &amp;lt;iqn name=&#039;iqn.2023-10.world.srv:node01.initiator01&#039;/&amp;gt;
      &amp;lt;/initiator&amp;gt;
    &amp;lt;/source&amp;gt;
    &amp;lt;target dev=&#039;sdc&#039; bus=&#039;sata&#039;/&amp;gt;
&amp;lt;/disk&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Performance+Impact&quot; name=&quot;Performance+Impact&quot;&gt;Performance Impact&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/metrics.png&quot; alt=&quot;metrics&quot; /&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Protocol&lt;/th&gt;
&lt;th&gt;Emulation&lt;/th&gt;
&lt;th style=&quot;text-align: right;&quot;&gt;512 Reads (MB/s)&lt;/th&gt;
&lt;th style=&quot;text-align: right;&quot;&gt;512 Writes (MB/s)&lt;/th&gt;
&lt;th style=&quot;text-align: right;&quot;&gt;1MB Reads (MB/s)&lt;/th&gt;
&lt;th style=&quot;text-align: right;&quot;&gt;1MB Writes (MB/s)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;iSCSI&lt;/td&gt;
&lt;td&gt;sata&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;174.0&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;11.5&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;316.0&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;131.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iSCSI&lt;/td&gt;
&lt;td&gt;virtio&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;201.0&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;15.1&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;317.0&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;133.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;nbd&lt;/td&gt;
&lt;td&gt;sata&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;164.0&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;12.7&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;308.0&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;130.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;nbd&lt;/td&gt;
&lt;td&gt;virtio&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;210.0&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;20.0&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;347.0&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;135.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Direct&lt;/td&gt;
&lt;td&gt;sata&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;162.0&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;12.7&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;317&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;339.0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;iSCSI with SATA emulation provides the most compatibility&lt;/li&gt;
&lt;li&gt;NBD with virtio emulation provides the highest performance.&lt;/li&gt;
&lt;li&gt;iSCSI with SATA has the highest per IOP overhead.&lt;/li&gt;
&lt;li&gt;nbd has lower protocol overhead than iSCSI&lt;/li&gt;
&lt;li&gt;virtio has better virtualization performance than sata emulation&lt;/li&gt;
&lt;li&gt;For large data transfers overhead ceases to be  relevant.&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Documenting Architecture Decisions</title>
<link href="https://www.0ink.net/posts/2025/2025-02-01-doc-adr.html"></link>
<id>urn:uuid:37246d1f-a1b8-dbab-0687-0a3ef3c6e7c2</id>
<updated>2024-12-13T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Context
Approach
Format
Conclusion

...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Context&quot;&gt;Context&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Approach&quot;&gt;Approach&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Format&quot;&gt;Format&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;This is a modified version of &lt;a href=&quot;https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions&quot;&gt;Documenting Archittecture Decisions&lt;/a&gt; by Michael Nygard.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/adr-lo.png&quot; alt=&quot;Decision Making&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;Context&quot; name=&quot;Context&quot;&gt;Context&lt;/h2&gt;
&lt;p&gt;Architecture for agile projects has to be described and defined differently. Not all decisions
will be made at once, nor will all of them be done when the project begins.&lt;/p&gt;
&lt;p&gt;Agile methods are not opposed to documentation, only to valueless documentation. Documents that
assist the team itself can have value, but only if they are kept up to date. Large documents are
never kept up to date. Small, modular documents have at least a chance at being updated.&lt;/p&gt;
&lt;p&gt;Nobody ever reads large documents, either. Most developers have been on at least one project where
the specification document was larger (in bytes) than the total source code size. Those documents
are too large to open, read, or update. Bite sized pieces are easier for for all stakeholders to
consume.&lt;/p&gt;
&lt;p&gt;One of the hardest things to track during the life of a project is the motivation behind certain
decisions. A new person coming on to a project may be perplexed, baffled, delighted, or infuriated
by some past decision. Without understanding the rationale or consequences, this person has only
two choices:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Blindly accept the decision. &lt;br /&gt;This response may be OK, if the decision is still valid. It may not be good, however, if the
context has changed and the decision should really be revisited. If the project accumulates
too many decisions accepted without understanding, then the development team becomes afraid
to change anything and the project collapses under its own weight.&lt;/li&gt;
&lt;li&gt;Blindly change it. &lt;br /&gt;Again, this may be OK if the decision needs to be reversed. On the other hand, changing the
decision without understanding its motivation or consequences could mean damaging the project&#039;s
overall value without realizing it. (E.g., the decision supported a non-functional requirement
that hasn&#039;t been tested yet.)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It&#039;s better to avoid either blind acceptance or blind reversal.&lt;/p&gt;
&lt;h2 id=&quot;Approach&quot; name=&quot;Approach&quot;&gt;Approach&lt;/h2&gt;
&lt;p&gt;We will keep a collection of records for &lt;em&gt;&amp;quot;architecturally significant&amp;quot;&lt;/em&gt; decisions: those that
affect the structure, non-functional characteristics, dependencies, interfaces, or construction
techniques.&lt;/p&gt;
&lt;p&gt;An architecture decision record is a short text file in a format similar to an Alexandrian
pattern. (Though the decisions themselves are not necessarily patterns, they share the
characteristic balancing of forces.) Each record describes a set of forces and a single
decision in response to those forces. Note that the decision is the central piece here, so
specific forces may appear in multiple ADRs.&lt;/p&gt;
&lt;p&gt;We will keep ADRs in the project repository under &lt;code&gt;doc/arch/YYYY-MM-DD-title.md&lt;/code&gt;.  Where
&lt;code&gt;YYYY-MM-DD&lt;/code&gt; is the date when the issue was first brought up (not necessarily resolved).&lt;/p&gt;
&lt;p&gt;We should Markdown because it is a lightweight text formatting language.&lt;/p&gt;
&lt;p&gt;If a decision is reversed, we will keep the old one around, but mark it as superseded.
(It&#039;s still relevant to know that it was the decision, but is &lt;em&gt;no longer&lt;/em&gt; the decision.)&lt;/p&gt;
&lt;h2 id=&quot;Format&quot; name=&quot;Format&quot;&gt;Format&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Header:
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;---
title: short noun phrases
status: wip/proposed/accepted/superseded
---&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Title: These documents have names that are short noun phrases. For example, &amp;quot;ADR 1: Deployment on
Ruby on Rails 3.0.10&amp;quot; or &amp;quot;ADR 9: LDAP for Multitenant Integration&amp;quot;.&lt;/li&gt;
&lt;li&gt;Status: A decision may be &amp;quot;proposed&amp;quot; if the project stakeholders haven&#039;t agreed with
it yet, or &amp;quot;accepted&amp;quot; once it is agreed. If a later ADR changes or reverses a decision,
it may be marked as &amp;quot;deprecated&amp;quot; or &amp;quot;superseded&amp;quot; with a reference to its replacement.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;# Context&lt;/code&gt; &lt;br /&gt;This section describes the forces at play, including technological, political, social, and
project local. These forces are probably in tension, and should be called out as such. The
language in this section is value-neutral. It is simply describing facts.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;# Decision&lt;/code&gt; &lt;br /&gt;This section describes our response to these forces. It is stated in full sentences, with active
voice. &amp;quot;We will ...&amp;quot;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;# Alternatives&lt;/code&gt; &lt;br /&gt;Other options being considered.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;# Justification&lt;/code&gt;&lt;br /&gt;Explanation on why decision was made.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;# Consequences&lt;/code&gt;&lt;br /&gt;This section describes the resulting context, after applying the decision. All consequences
should be listed here, not just the &amp;quot;positive&amp;quot; ones. A particular decision may have positive,
negative, and neutral consequences, but all of them affect the team and project in the future.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The whole document should be one or two pages long. We will write each ADR as if it is a
conversation with a future developer. This requires good writing style, with full sentences
organized into paragraphs. Bullets are acceptable only for visual style, not as an excuse for
writing sentence fragments. (Bullets kill people, even PowerPoint bullets.)&lt;/p&gt;
&lt;h2 id=&quot;Conclusion&quot; name=&quot;Conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;One ADR describes one significant decision for a specific project. It should be something
that has an effect on how the rest of the project will run.&lt;/p&gt;
&lt;p&gt;The consequences of one ADR are very likely to become the context for subsequent ADRs. This
is also similar to Alexander&#039;s idea of a pattern language: the large-scale responses create
spaces for the smaller scale to fit into.&lt;/p&gt;
&lt;p&gt;Developers and project stakeholders can see the ADRs, even as the team composition changes
over time.&lt;/p&gt;
&lt;p&gt;The motivation behind previous decisions is visible for everyone, present and future. Nobody
is left scratching their heads to understand, &amp;quot;What were they thinking?&amp;quot; and the time to
change old decisions will be clear from changes in the project&#039;s context.&lt;/p&gt;</content>
</entry>
<entry>
<title>Alpine Boot</title>
<link href="https://www.0ink.net/posts/2025/2025-01-23-alpine-boot.html"></link>
<id>urn:uuid:affabb09-aa6f-a924-efb3-bdcf055701fc</id>
<updated>2025-01-23T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
This is a quick note.  In Alpine linux you can run start-up scripts by enabling the
local service:
rc-update add local default
When this is enabled, it will run all executable scripts that end with .start on
start-up and scripts that end with .stop on shutdown.
...]]></summary>
<content type="html">&lt;p&gt;&lt;img src=&quot;/images/2025/alpinelinux-logo.png&quot; alt=&quot;icon&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This is a quick note.  In Alpine linux you can run start-up scripts by enabling the
&lt;code&gt;local&lt;/code&gt; service:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;rc-update add local default&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When this is enabled, it will run all executable scripts that end with &lt;code&gt;.start&lt;/code&gt; on
start-up and scripts that end with &lt;code&gt;.stop&lt;/code&gt; on shutdown.&lt;/p&gt;
&lt;p&gt;By default, these scripts would run silently and you will see:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;* Starting local ... [ ok ] &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;during boot-up if things went well.  If there was an error you would see:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;* Starting local ... [ !! ] &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To see more details on what happened create the file &lt;code&gt;/etc/conf.d/local&lt;/code&gt; with
the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# /etc/conf.d/local
rc_verbose=yes&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>Y2K38 filesystems</title>
<link href="https://www.0ink.net/posts/2025/2025-01-15-Y2038-fs.html"></link>
<id>urn:uuid:a681c105-09b8-f8dc-a95b-d992fbbb752e</id>
<updated>2024-11-22T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Background
What to do
xfs
ext4

...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Background&quot;&gt;Background&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#What+to+do&quot;&gt;What to do&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#xfs&quot;&gt;xfs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#ext4&quot;&gt;ext4&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;This is a small note...&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/y2038bug.png&quot; alt=&quot;y2038 bug&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This happens to me some times.  I mount a filesystem and I get the warning:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;xfs filesystem being mounted at /mntimg supports timestamps until 2038-01-19 (0x7fffffff)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This has to do with the &lt;strong&gt;year 2038&lt;/strong&gt; problem.&lt;/p&gt;
&lt;h2 id=&quot;Background&quot; name=&quot;Background&quot;&gt;Background&lt;/h2&gt;
&lt;p&gt;The year 2038 problem (also known as &lt;strong&gt;Y2038&lt;/strong&gt;, &lt;strong&gt;Y2K38&lt;/strong&gt;, &lt;strong&gt;Y2K38 superbug&lt;/strong&gt;
or the &lt;strong&gt;Epochalypse&lt;/strong&gt;) is a time computing problem that leaves some computer systems
unable to represent times after 03:14:07 UTC on 19 January 2038.&lt;/p&gt;
&lt;p&gt;The problem exists in systems which measure Unix time -- the number of seconds
elapsed since the &lt;strong&gt;Unix epoch&lt;/strong&gt; (00:00:00 UTC on 1 January 1970) -- and store it in a
signed 32-bit integer. The data type is only capable of representing integers between
-(2&lt;sup&gt;31&lt;/sup&gt;) and 2&lt;sup&gt;31&lt;/sup&gt; − 1, meaning the latest time that can be properly encoded
is 2&lt;sup&gt;31&lt;/sup&gt; − 1 seconds after epoch (03:14:07 UTC on 19 January 2038). Attempting to
increment to the following second (03:14:08) will cause the integer to overflow,
setting its value to -(2&lt;sup&gt;31&lt;/sup&gt;) which systems will interpret as 2&lt;sup&gt;31&lt;/sup&gt; seconds
before epoch (20:45:52 UTC on 13 December 1901). The problem is similar in nature
to the year 2000 problem, the difference being the Year 2000 problem had to do with
base 10 numbers, whereas the Year 2038 problem involves base 2 numbers.&lt;/p&gt;
&lt;p&gt;See &lt;a href=&quot;https://en.wikipedia.org/wiki/Year_2038_problem&quot;&gt;wikipedia article&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;What+to+do&quot; name=&quot;What+to+do&quot;&gt;What to do&lt;/h2&gt;
&lt;p&gt;Of course, you can just ignore it, it is merely a warning and not an error.  If
you want to keep your logs clean of these, you can update your filesystems
or just create new ones.  Ironically, this only affects UNIX style
filesystems such as &lt;a href=&quot;https://en.wikipedia.org/wiki/XFS&quot;&gt;xfs&lt;/a&gt; and &lt;a href=&quot;https://en.wikipedia.org/wiki/Ext4&quot;&gt;ext4&lt;/a&gt;.  It does &lt;em&gt;not&lt;/em&gt; affect
other filesystems like &lt;a href=&quot;https://en.wikipedia.org/wiki/File_Allocation_Table&quot;&gt;vfat&lt;/a&gt; used by MSDOS.&lt;/p&gt;
&lt;p&gt;For &lt;a href=&quot;https://en.wikipedia.org/wiki/XFS&quot;&gt;xfs&lt;/a&gt; and &lt;a href=&quot;https://en.wikipedia.org/wiki/Ext4&quot;&gt;ext4&lt;/a&gt;, the Linux Kernel introduced patches back
in version 5.10 circa 2021 to address these.&lt;/p&gt;
&lt;h2 id=&quot;xfs&quot; name=&quot;xfs&quot;&gt;xfs&lt;/h2&gt;
&lt;p&gt;For &lt;code&gt;xfs&lt;/code&gt;, when you createa new file system it should default now to using
64 bit timestamps.  Otherwise you can force it with this command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkfs.xfs -m bigtime=1 device&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you have existing file systems you can convert them with this process:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;xfs_repair -n&lt;/code&gt; to make sure that there are no errors&lt;/li&gt;
&lt;li&gt;Run:
&lt;pre&gt;&lt;code&gt;xfs_amdin -O bigtime=1 device&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;ext4&quot; name=&quot;ext4&quot;&gt;ext4&lt;/h2&gt;
&lt;p&gt;Check if the filesystem needs to be upgraded:&lt;/p&gt;
&lt;p&gt;&lt;code&gt; $ tune2fs -l device | grep &quot;Inode size:&quot; Inode size:           128 &lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Where an inode size of &lt;code&gt;128&lt;/code&gt; is insufficient beyond 2038 and an inode size of
&lt;code&gt;256&lt;/code&gt; is what you want.&lt;/p&gt;
&lt;p&gt;Current version of &lt;code&gt;mkfs&lt;/code&gt; will create a filesystem with the appropriate inode
size.  Otherwise, you can use the command with the right inode size:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkfs.ext4 -I 256 device&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unfortunately, there is no safe way to change an existing filesystem.  The
current version of &lt;code&gt;e2fsprogs&lt;/code&gt; does accept a &lt;code&gt;-I 256&lt;/code&gt; option, however,
the functionality is disabled when &lt;code&gt;flex_bg&lt;/code&gt; (which is &lt;strong&gt;on&lt;/strong&gt; by default) is
enabled.  But even making sure that &lt;code&gt;flex_bg&lt;/code&gt; is off, resizing
the inodes fails.&lt;/p&gt;
&lt;p&gt;The best way to do the conversion is then to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;backup filesystem&lt;/li&gt;
&lt;li&gt;re-create filesystem&lt;/li&gt;
&lt;li&gt;restore backup&lt;/li&gt;
&lt;/ol&gt;</content>
</entry>
<entry>
<title>NEW YEAR 2025</title>
<link href="https://www.0ink.net/posts/2025/2025-01-01-newyear.html"></link>
<id>urn:uuid:2b63b346-6dcd-3dba-2dfa-68db621785f5</id>
<updated>2024-10-22T00:00:00+02:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[This year this Blog would have been up and running for 12 years!

Happy new year sticker created by Icon home - Flaticon
...]]></summary>
<content type="html">&lt;p&gt;This year this Blog would have been up and running for 12 years!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2025/balloon.png&quot; alt=&quot;HAPPY NEW YEAR&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.flaticon.com/free-stickers/happy-new-year&quot;&gt;Happy new year sticker created by Icon home - Flaticon&lt;/a&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>Single Page Application</title>
<link href="https://www.0ink.net/posts/2024/2024-12-31-spa.html"></link>
<id>urn:uuid:743e50ff-a7ff-bfec-3ca1-bb2b8904b7f1</id>
<updated>2024-12-13T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
As the last post of the year, I am posting my attempt at a simple &quot;Single Page Application&quot;.
A single-page application (SPA) is a web application or website that interacts with the
user by dynamically rewriting the current web page, instead of the default method of loading
entire new pages. The goal is faster transitions that make the website feel more like a native
app.
...]]></summary>
<content type="html">&lt;p&gt;&lt;img src=&quot;/images/2024/spa/icon.png&quot; alt=&quot;icon&quot; /&gt;&lt;/p&gt;
&lt;p&gt;As the last post of the year, I am posting my attempt at a simple &amp;quot;Single Page Application&amp;quot;.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;single-page application (SPA)&lt;/strong&gt; is a web application or website that interacts with the
user by dynamically rewriting the current web page, instead of the default method of loading
entire new pages. The goal is faster transitions that make the website feel more like a native
app.&lt;/p&gt;
&lt;p&gt;In a SPA, a page refresh never occurs; instead, all necessary HTML, JavaScript, and CSS
code is either retrieved by the browser with a single page load, or the appropriate resources
are dynamically loaded and added to the page as necessary, usually in response to user actions. &lt;/p&gt;
&lt;p&gt;From &lt;a href=&quot;https://en.wikipedia.org/wiki/Single-page_application&quot;&gt;wikipedia&lt;/a&gt; article.&lt;/p&gt;
&lt;p&gt;For my simple &lt;strong&gt;single-page application&lt;/strong&gt;, I wanted a web page that would present a form
that will let the user enter some input parameters, then click a button and a page
showing a QR code base on the input parameters will be displayed.  If the user were
to scan the QR code, then the page will be open showing some payload text and an optional
URL.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;SPA&lt;/strong&gt; makes use of these libraries:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/agnoster/base32-js&quot;&gt;base32-js&lt;/a&gt;: Used to encode the URL to escape special characters.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/davidshimjs/qrcodejs&quot;&gt;qrcodejs&lt;/a&gt;: QR-code generation library&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To make things easy for me, I chose to limit the javascript to only those
libraries and using only:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/jscsshtml.png&quot; alt=&quot;icons&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;HTML&lt;/li&gt;
&lt;li&gt;JavaScript&lt;/li&gt;
&lt;li&gt;CSS&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There is a single HTML that gets loaded at the beginning and contains all the
interactive screens:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Loading screen &lt;br /&gt;Initial screen, usually gets replaced immediatly so you should hardly see it.&lt;/li&gt;
&lt;li&gt;home screen &lt;br /&gt;Default screen, showing a form to get input parameters.&lt;/li&gt;
&lt;li&gt;qrdisplay screen &lt;br /&gt;Screen showing the QR code&lt;/li&gt;
&lt;li&gt;error screen &lt;br /&gt;Screen showing that decoding failed for some reason&lt;/li&gt;
&lt;li&gt;decoded screen &lt;br /&gt;Screen showing the decoded QR code&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On load all screens except for &lt;code&gt;Loading&lt;/code&gt; are disabled (CSS style &lt;code&gt;display: none&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;After loading, &lt;code&gt;script.js&lt;/code&gt; will check if the URL contains a &lt;code&gt;#&lt;/code&gt; character
indicating there is a &lt;em&gt;fragment identifier&lt;/em&gt;.  If found, it will decode
and show the contents of the &lt;em&gt;fragment identifier&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;If there was no &lt;em&gt;fragment identifier&lt;/em&gt;, the home screen will be shown.&lt;/p&gt;
&lt;p&gt;Note that all the interaction happens in the web browser.  Even entering the
URL with the &lt;em&gt;fragment identifier&lt;/em&gt;, will only send the web page request
to the server.  The &lt;em&gt;fragment identifier&lt;/em&gt; remains local to the browser
and is never send to the network.&lt;/p&gt;
&lt;p&gt;The script source can be found in &lt;a href=&quot;https://github.com/alejandroliu/0ink.net/tree/main/src/content/images/2024/spa&quot;&gt;github&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can check-it out in action in this &lt;a href=&quot;https://0ink.net/images/2024/spa/spa.html&quot;&gt;demo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/spa/screenshots/home.png&quot; alt=&quot;Home Screen&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/spa/screenshots/qrshow.png&quot; alt=&quot;QRCode Screen&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/spa/screenshots/decoded.png&quot; alt=&quot;Decoded Screen&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So, that is for this year.  See you next in year 2025!.&lt;/strong&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>Fitbit reset</title>
<link href="https://www.0ink.net/posts/2024/2024-12-20-fitbit-stuff.html"></link>
<id>urn:uuid:66f232b2-1990-6bed-f8f2-f3040707edd3</id>
<updated>2024-12-21T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[

How to reset your Fibit Charge 5, 6 or Fitbit Luxe?
You can reset your Fitbit Charge 5, Charge 6, or Fitbit Luxe in 2 ways:

Soft reset: restart your Fitbit. You keep your data.
...]]></summary>
<content type="html">&lt;p&gt;&lt;img src=&quot;/images/2024/fitbit-charge.png&quot; alt=&quot;charge&quot; /&gt;
&lt;img src=&quot;/images/2024/fitbit-luxe.png&quot; alt=&quot;luxe&quot; /&gt;&lt;/p&gt;
&lt;p&gt;How to reset your Fibit Charge 5, 6 or Fitbit Luxe?&lt;/p&gt;
&lt;p&gt;You can reset your Fitbit Charge 5, Charge 6, or Fitbit Luxe in 2 ways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Soft reset: restart your Fitbit. You keep your data.&lt;/li&gt;
&lt;li&gt;Hard reset: revert to factory setting. Your data is removed from your Fitbit.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Method 1:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Swipe down from the start screen (with the clock face on it).&lt;/li&gt;
&lt;li&gt;Go to &lt;em&gt;Settings.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Tap &lt;em&gt;Restart device.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Tap &lt;em&gt;Restart&lt;/em&gt; again.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Doesn&#039;t the restart solve anything? Follow the following steps.&lt;br /&gt;&lt;strong&gt;Note:&lt;/strong&gt; You will lose all your data:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Connect your Fitbit to your charger.&lt;/li&gt;
&lt;li&gt;Press the button on the flat side of the charger 3 times. This flat piece is fixed to
the USB-A connector. Wait 1 second after each press.&lt;/li&gt;
&lt;li&gt;Wait 10 seconds until the Fitbit logo appears.&lt;/li&gt;
&lt;/ol&gt;</content>
</entry>
<entry>
<title>Python development tips 2024</title>
<link href="https://www.0ink.net/posts/2024/2024-12-15-pydev-tips.html"></link>
<id>urn:uuid:dbe0c116-a2c4-a404-1074-4a3117089553</id>
<updated>2025-05-03T00:00:00+02:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Local install packages
Better debugging
Built-in exceptions
Adding site specific customizations
Constants
...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Local+install+packages&quot;&gt;Local install packages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Better+debugging&quot;&gt;Better debugging&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Built-in+exceptions&quot;&gt;Built-in exceptions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Adding+site+specific+customizations&quot;&gt;Adding site specific customizations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Constants&quot;&gt;Constants&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Using+Environment+Variables+for+Configuration&quot;&gt;Using Environment Variables for Configuration&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Why+use+environment+variables+for+configuring+Python+applications%3F&quot;&gt;Why use environment variables for configuring Python applications?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#How+are+environment+variables+in+Python+populated%3F&quot;&gt;How are environment variables in Python populated?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#How+to+get+a+Python+environment+variable&quot;&gt;How to get a Python environment variable&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Option+1%3A+Required+with+no+default+value&quot;&gt;Option 1: Required with no default value&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Option+2%3A+Required+with+default+value&quot;&gt;Option 2: Required with default value&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Option+3%3A+Conditional+logic+if+value+exists&quot;&gt;Option 3: Conditional logic if value exists&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#How+to+set+a+Python+environment+variable&quot;&gt;How to set a Python environment variable&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#How+to+delete+a+Python+environment+variable&quot;&gt;How to delete a Python environment variable&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Why+default+values+for+environment+variables+should+be+avoided&quot;&gt;Why default values for environment variables should be avoided&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Using+a+.env+file+for+Python+environment+variables&quot;&gt;Using a .env file for Python environment variables&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/python.png&quot; alt=&quot;python&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;Local+install+packages&quot; name=&quot;Local+install+packages&quot;&gt;Local install packages&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Install using a venv and pip
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python3 -m venv --system-site-packages tmpenv
. tmpenv/bin/activate
pip install icecream&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Copy the resulting python lib/python3.12 to /usr/local/lib/python3 (omitting pip)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Normally, copying virtual environments would not work as paths are hard-coded in
many files.  However, here we are only copying the files in &lt;code&gt;lib/python3.1&lt;/code&gt; to
&lt;code&gt;/usr/local/lib/python3&lt;/code&gt;.  Most python distributions would automatically pick up
files from there.  However, you can force it using &lt;code&gt;PYTHONPATH&lt;/code&gt; environment variable.&lt;/p&gt;
&lt;h2 id=&quot;Better+debugging&quot; name=&quot;Better+debugging&quot;&gt;Better debugging&lt;/h2&gt;
&lt;p&gt;So instead of using print for outputing debuging code, you can use icecream:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/gruns/icecream&quot;&gt;icecream&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Interestingly, there are versions for other languages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/ntzm/icecream-php&quot;&gt;for PHP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jtplaarj/IceCream-Bash&quot;&gt;for BASH&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It looks very interesting as it is a better way to get debugging output.  I
am thinking of using it like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;try:
    from icecream import ic
    ic.configureOutput(includeContext=True) # Optional... shows source and line numbers
except ImportError:  # Graceful fallback if IceCream isn&#039;t installed.
    ic = lambda *a: None if not a else (a[0] if len(a) == 1 else a)  # noqa&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Requires: &lt;code&gt;python3-executing&lt;/code&gt; &lt;code&gt;python3-asttokens&lt;/code&gt; &lt;code&gt;python3-colorama&lt;/code&gt;&lt;/p&gt;
&lt;h2 id=&quot;Built-in+exceptions&quot; name=&quot;Built-in+exceptions&quot;&gt;Built-in exceptions&lt;/h2&gt;
&lt;p&gt;Python prefers the use of exceptions instead of return values to indicate errors.
Since you wouldn&#039;t want to create a new exception class for every error you may
need to raise, it is good to have a list of Python&#039;s built-in exception handy.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.python.org/3/library/exceptions.html&quot;&gt;Python Built-in exceptions&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;List of exceptions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Generic exceptions
&lt;ul&gt;
&lt;li&gt;Exception&lt;/li&gt;
&lt;li&gt;ArithmeticError&lt;/li&gt;
&lt;li&gt;BufferError&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Specific exceptions
&lt;ul&gt;
&lt;li&gt;AssertionError - related to &lt;code&gt;assert&lt;/code&gt; calls&lt;/li&gt;
&lt;li&gt;AttributeError - related to object attributes&lt;/li&gt;
&lt;li&gt;EOFError&lt;/li&gt;
&lt;li&gt;GeneratorError - raised when a &lt;code&gt;generator&lt;/code&gt; or a &lt;code&gt;corroutine&lt;/code&gt; closes&lt;/li&gt;
&lt;li&gt;ImportError&lt;/li&gt;
&lt;li&gt;ModuleNotFoundError&lt;/li&gt;
&lt;li&gt;IndexError - for numeric subscripts (i.e. &lt;code&gt;list&lt;/code&gt; or &lt;code&gt;tupples&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;KeyError - for &lt;code&gt;dict&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;KeyboardInterrupt - &lt;kbd&gt;Control+C&lt;/kbd&gt;&lt;/li&gt;
&lt;li&gt;NotImplementedError&lt;/li&gt;
&lt;li&gt;OSError&lt;/li&gt;
&lt;li&gt;OverflowError&lt;/li&gt;
&lt;li&gt;RecursionError&lt;/li&gt;
&lt;li&gt;ReferenceError&lt;/li&gt;
&lt;li&gt;RuntimeError - this is the &amp;quot;others&amp;quot; exception.&lt;/li&gt;
&lt;li&gt;StopIteration - raised by &lt;code&gt;next()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;StopAsyncIteration&lt;/li&gt;
&lt;li&gt;SyntaxError&lt;/li&gt;
&lt;li&gt;IndentationError&lt;/li&gt;
&lt;li&gt;TabError&lt;/li&gt;
&lt;li&gt;SystemError - An Python interpreter internal error.  These should be reported to the
python maintainers.&lt;/li&gt;
&lt;li&gt;SystemExit - raised by &lt;code&gt;sys.exit()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;TypeError&lt;/li&gt;
&lt;li&gt;UnboundLocalError&lt;/li&gt;
&lt;li&gt;UnicodeError&lt;/li&gt;
&lt;li&gt;UnicodeEncodeError&lt;/li&gt;
&lt;li&gt;UnicodeDecodeError&lt;/li&gt;
&lt;li&gt;UnicodeTranslateError&lt;/li&gt;
&lt;li&gt;ValueError&lt;/li&gt;
&lt;li&gt;ZeroDivisionError&lt;/li&gt;
&lt;li&gt;EnvironmentError, IOError, WindowsError - Only applicable to Microsoft Windows&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;OS exceptions: These are subclasses of &lt;code&gt;OSError&lt;/code&gt;.
&lt;ul&gt;
&lt;li&gt;BlockingIOError&lt;/li&gt;
&lt;li&gt;ChildProcessError&lt;/li&gt;
&lt;li&gt;ConnectionError&lt;/li&gt;
&lt;li&gt;BrokenPipeError&lt;/li&gt;
&lt;li&gt;ConnectionAbortedError&lt;/li&gt;
&lt;li&gt;ConnectionRefusedError&lt;/li&gt;
&lt;li&gt;ConnectionResetError&lt;/li&gt;
&lt;li&gt;FileExistsError&lt;/li&gt;
&lt;li&gt;FileNotFoundError&lt;/li&gt;
&lt;li&gt;InterruptedError&lt;/li&gt;
&lt;li&gt;IsADirectoryError&lt;/li&gt;
&lt;li&gt;NotADirectoryError&lt;/li&gt;
&lt;li&gt;PermissionError&lt;/li&gt;
&lt;li&gt;ProcessLookupError&lt;/li&gt;
&lt;li&gt;TimeoutError&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are more exceptions but these are not commonly used.&lt;/p&gt;
&lt;p&gt;For catching exception, it is useful to refer to the &lt;a href=&quot;https://docs.python.org/3/library/exceptions.html#exception-hierarchy&quot;&gt;Exception class tree&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Raising exceptions uses the following syntax:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;if x &amp;lt; 0:
  raise ValueError(f&#039;Sorry, {x} is not valid&#039;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And you can then catch it with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;try:
  execute_code()
except ValueError as e:
  print(e)
except:
  print(&quot;generic&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Adding+site+specific+customizations&quot; name=&quot;Adding+site+specific+customizations&quot;&gt;Adding site specific customizations&lt;/h2&gt;
&lt;p&gt;When python starts, it will load a module called &lt;a href=&quot;https://docs.python.org/3/library/site.html#module-sitecustomize&quot;&gt;sitecustomize&lt;/a&gt;
where site specific customizations can be done.  My idea is to have a script
create this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/sh
type python || exit 1
site_dir=$(python -m site | grep site-packages | tr -d &quot;&#039;,&quot; | grep -v USER_SITE | xargs)
if [ -z &quot;$site_dir&quot; ] ; then
  echo &quot;Unable to determine site dir&quot; 1&amp;gt;&amp;amp;2
  exit 2
fi
if [ ! -d &quot;$site_dir ] ; then
  echo &quot;$site_dir: not found&quot; 1&amp;gt;&amp;amp;2
  exit 3
fi
if [ ! -w &quot;$site_dir&quot; ] ; then
  echo &quot;$site_dir: no write access&quot; 1&amp;gt;&amp;amp;2
  exit 4
fi

dd of=&quot;$site_dir/sitecustomize.py&quot; &amp;lt;&amp;lt;_EOF_
  import site
  site.addsitedir(&quot;/usr/local/lib/python3&quot;)
_EOF_&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Constants&quot; name=&quot;Constants&quot;&gt;Constants&lt;/h2&gt;
&lt;p&gt;While Python doesn&#039;t have a constant of constants, the convention in Python is
to write the name in capital letters with underscores separating words.&lt;/p&gt;
&lt;p&gt;By using capital letters only, you&#039;re communicating that the current name is
intended to be treated as a constant—or more precisely, as a variable that never
changes. So, other Python developers will know that and hopefully won&#039;t perform
any assignment operation on the variable at hand.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: Python doesn&#039;t support constants or non-reassignable names.
Using uppercase letters is just a convention, and it doesn&#039;t prevent developers
from assigning new values to your constant. So, any programmer working on your code
needs to be careful and never write code that changes the values of constants.
Remember this rule because you also need to follow it.&lt;/p&gt;
&lt;p&gt;While this may seem pointless (or redundant) this has the following benefits:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Advantage&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Improved readability&lt;/td&gt;
&lt;td&gt;A descriptive name representing a given value throughout a program is always more readable and explicit than the bare-bones value itself. For example, it&#039;s easier to read and understand a constant named &lt;code&gt;MAX_SPEED&lt;/code&gt; than the concrete speed value itself.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clear communication of intent&lt;/td&gt;
&lt;td&gt;Most people will assume that &lt;code&gt;3.14&lt;/code&gt; may refer to the &lt;code&gt;Pi&lt;/code&gt; constant. However, using the &lt;code&gt;Pi&lt;/code&gt;, &lt;code&gt;pi&lt;/code&gt;, or &lt;code&gt;PI&lt;/code&gt; name will communicate your intent more clearly than using the value directly. This practice will allow other developers to understand your code quickly and accurately.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Better maintainability&lt;/td&gt;
&lt;td&gt;Constants enable you to use the same name to identify the same value throughout your code. If you need to update the constant&#039;s value, then you don&#039;t have to change every instance of the value. You just have to change the value in a single place: the constant definition. This improves your code&#039;s maintainability.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lower risk of errors&lt;/td&gt;
&lt;td&gt;A constant representing a given value throughout a program is less error-prone than several explicit instances of the value. Say that you use different precision levels for &lt;code&gt;Pi&lt;/code&gt; depending on your target calculations. You&#039;ve explicitly used the values with the required precision for every calculation. If you need to change the precision in a set of calculations, then replacing the values can be error-prone because you can end up changing the wrong values. It&#039;s safer to create different constants for different precision levels and change the code in a single place.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reduced debugging needs&lt;/td&gt;
&lt;td&gt;Constants will remain unchanged during the program&#039;s lifetime. Because they&#039;ll always have the same value, they shouldn&#039;t cause errors and bugs. This feature may not be necessary in small projects, but it may be crucial in large projects with multiple developers. Developers won&#039;t have to invest time debugging the current value of any constant.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Thread-safe data storage&lt;/td&gt;
&lt;td&gt;Constants can only be accessed, not written. This feature makes them thread-safe objects, which means that several threads can simultaneously use a constant without the risk of corrupting or losing the underlying data.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Detect typos&lt;/td&gt;
&lt;td&gt;A very common mistake specially for string constants is to mispell the value itself.  By making the value a constant, if the constant is mispelled, an error will be raised&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;From &lt;a href=&quot;https://realpython.com/python-constants/#why-use-constants&quot;&gt;realpython&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;Using+Environment+Variables+for+Configuration&quot; name=&quot;Using+Environment+Variables+for+Configuration&quot;&gt;Using Environment Variables for Configuration&lt;/h2&gt;
&lt;p&gt;Learn how experienced developers use environment variables in Python, including managing
default values and typecasting.&lt;/p&gt;
&lt;p&gt;From &lt;a href=&quot;https://www.doppler.com/blog/environment-variables-in-python&quot;&gt;Doppler&lt;/a&gt; blog.&lt;/p&gt;
&lt;p&gt;As a developer, you’ve likely used environment variables in the command line or shell scripts,
but have you used them as a way of configuring your Python applications?&lt;/p&gt;
&lt;p&gt;This guide will show you all the code necessary for getting, setting, and loading environment
variables in Python, including how to use them for supplying application config and secrets.&lt;/p&gt;
&lt;h3 id=&quot;Why+use+environment+variables+for+configuring+Python+applications%3F&quot; name=&quot;Why+use+environment+variables+for+configuring+Python+applications%3F&quot;&gt;Why use environment variables for configuring Python applications?&lt;/h3&gt;
&lt;p&gt;Before digging into how to use environment variables in Python, it&#039;s important to
understand why they&#039;re arguably the best way to configure applications. The main
benefits are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Deploy your application in any environment without code changes&lt;/li&gt;
&lt;li&gt;Ensures secrets such as API keys are not leaked into source code&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Environment variables have the additional benefit of abstracting from your application how
config and secrets are supplied.&lt;/p&gt;
&lt;p&gt;Finally, environment variables enable your application to run anywhere, whether it&#039;s for local
development on macOS, a container in a Kubernetes Pod, or platforms such as Heroku or Vercel.&lt;/p&gt;
&lt;p&gt;Here are some examples of using environment variables to configure a Python script or application:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Set &lt;code&gt;FLASK_ENV&lt;/code&gt; environment variable to &lt;code&gt;&quot;development&quot;&lt;/code&gt; to enable debug mode for a
Flask application&lt;/li&gt;
&lt;li&gt;Provide the &lt;code&gt;STRIPE_API_KEY&lt;/code&gt; environment variable for an Ecommerce site&lt;/li&gt;
&lt;li&gt;Supply the &lt;code&gt;DISCORD_TOKEN&lt;/code&gt; environment variable to a Discord bot app so it can join a server&lt;/li&gt;
&lt;li&gt;Set environment specific database variables such as &lt;code&gt;DB_USER&lt;/code&gt; and &lt;code&gt;DB_PASSWORD&lt;/code&gt; so database
credentials are not hard-coded&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;How+are+environment+variables+in+Python+populated%3F&quot; name=&quot;How+are+environment+variables+in+Python+populated%3F&quot;&gt;How are environment variables in Python populated?&lt;/h3&gt;
&lt;p&gt;When a Python process is created, the available environment variables populate the &lt;code&gt;os.environ&lt;/code&gt;
object which acts like a Python dictionary. This means that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Any environment variable modifications made after the Python process was created will not
be reflected in the Python process.&lt;/li&gt;
&lt;li&gt;Any environment variable changes made in Python do not affect environment variables in the
parent process.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now that you know how environment variables in Python are populated, let&#039;s look at how to access them.&lt;/p&gt;
&lt;h3 id=&quot;How+to+get+a+Python+environment+variable&quot; name=&quot;How+to+get+a+Python+environment+variable&quot;&gt;How to get a Python environment variable&lt;/h3&gt;
&lt;p&gt;Environment variables in Python are accessed using the &lt;code&gt;os.environ&lt;/code&gt; object.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;os.environ&lt;/code&gt; object seems like a dictionary but is different as values may only be
strings, plus it&#039;s not serializable to JSON.&lt;/p&gt;
&lt;p&gt;You&#039;ve got a few options when it comes to referencing the &lt;code&gt;os.environ&lt;/code&gt; object:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 1. Standard way

import os

# os.environ[&#039;VAR_NAME&#039;]

# 2. Import just the environ object

from os import environ

# environ[&#039;VAR_NAME&#039;]

# 3. Rename the `environ` to env object for more concise code

from os import environ as env

# env[&#039;VAR_NAME&#039;]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Accessing a specific environment variable in Python can be done in one of three ways, depending
upon what should happen if an environment variable does not exist.&lt;/p&gt;
&lt;p&gt;Let&#039;s explore with some examples.&lt;/p&gt;
&lt;h4 id=&quot;Option+1%3A+Required+with+no+default+value&quot; name=&quot;Option+1%3A+Required+with+no+default+value&quot;&gt;Option 1: Required with no default value&lt;/h4&gt;
&lt;p&gt;If your app should crash when an environment variable is not set, then access it directly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;print(os.environ[&#039;HOME&#039;]

# &amp;gt;&amp;gt; &#039;/home/dev&#039;

print(os.environ[&#039;DOES_NOT_EXIST&#039;]

# &amp;gt;&amp;gt; Will raise a KeyError exception&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For example, an application should fail to start if a required environment variable is not set,
and a default value can&#039;t be provided, e.g. a database password.&lt;/p&gt;
&lt;p&gt;If instead of the default &lt;code&gt;KeyError&lt;/code&gt; exception being raised (which doesn&#039;t communicate why
your app failed to start), you could capture the exception and print out a helpful message:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import os
import sys

# Ensure all required environment variables are set
try:  
  os.environ[&#039;API_KEY&#039;]
except KeyError: 
  print(&#039;[error]: `API_KEY` environment variable required&#039;)
  sys.exit(1)&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;Option+2%3A+Required+with+default+value&quot; name=&quot;Option+2%3A+Required+with+default+value&quot;&gt;Option 2: Required with default value&lt;/h4&gt;
&lt;p&gt;You can have a default value returned if an environment variable doesn&#039;t exist by using the
&lt;code&gt;os.environ.get&lt;/code&gt; method and supplying the default value as the second parameter:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;
# If HOSTNAME doesn&#039;t exist, presume local development and return localhost

print(os.environ.get(&#039;HOSTNAME&#039;, &#039;localhost&#039;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the variable doesn&#039;t exist and you use &lt;code&gt;os.environ.get&lt;/code&gt; without a default value, &lt;code&gt;None&lt;/code&gt;
is returned&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;assert os.environ.get(&#039;NO_VAR_EXISTS&#039;) == None&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;Option+3%3A+Conditional+logic+if+value+exists&quot; name=&quot;Option+3%3A+Conditional+logic+if+value+exists&quot;&gt;Option 3: Conditional logic if value exists&lt;/h4&gt;
&lt;p&gt;You may need to check if an environment variable exists, but don&#039;t necessarily care about its
value. For example, your application can be put in a &lt;em&gt;&amp;quot;Debug mode&amp;quot;&lt;/em&gt; if the &lt;code&gt;DEBUG&lt;/code&gt; environment
variable is set.&lt;/p&gt;
&lt;p&gt;You can check for just the existence of an environment variable:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;if &#039;DEBUG&#039; in os.environ:
  print(&#039;[info]: app is running in debug mode&#039;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or check to see it matches a specific value:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;if os.environ.get(&#039;DEBUG&#039;) == &#039;True&#039;:
  print(&#039;[info]: app is running in debug mode&#039;)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;How+to+set+a+Python+environment+variable&quot; name=&quot;How+to+set+a+Python+environment+variable&quot;&gt;How to set a Python environment variable&lt;/h3&gt;
&lt;p&gt;Setting an environment variable in Python is the same as setting a key on a dictionary:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;os.environ[&#039;TESTING&#039;] = &#039;true&#039;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What makes &lt;code&gt;os.environ&lt;/code&gt; different to a standard dictionary, is that only string values are allowed:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;os.environ[&#039;TESTING&#039;] = True
# &amp;gt;&amp;gt; TypeError: str expected, not bool&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In most cases, your application will only need to get environment variables, but there are use
cases for setting them as well.&lt;/p&gt;
&lt;p&gt;For example, constructing a &lt;code&gt;DB_URL&lt;/code&gt; environment variable on application start-up using
&lt;code&gt;DB_HOST&lt;/code&gt;, &lt;code&gt;DB_PORT&lt;/code&gt;, &lt;code&gt;DB_USER&lt;/code&gt;, &lt;code&gt;DB_PASSWORD&lt;/code&gt;, and &lt;code&gt;DB_NAME&lt;/code&gt; environment variables:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;os.environ[&#039;DB_URL&#039;] = &#039;psql://{user}:{password}@{host}:{port}/{name}&#039;.format(
  user=os.environ[&#039;DB_USER&#039;],
  password=os.environ[&#039;DB_PASSWORD&#039;],
  host=os.environ[&#039;DB_HOST&#039;],
  port=os.environ[&#039;DB_PORT&#039;],
  name=os.environ[&#039;DB_NAME&#039;]
)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another example is setting a variable to a default value based on the value of another variable:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Set DEBUG and TESTING to &#039;True&#039; if ENV is &#039;development&#039;
if os.environ.get(&#039;ENV&#039;) == &#039;development&#039;:
  os.environ.setdefault(&#039;DEBUG&#039;, &#039;True&#039;) # Only set to True if DEBUG not set
  os.environ.setdefault(&#039;TESTING&#039;, &#039;True&#039;) # Only set to True if TESTING not set&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;How+to+delete+a+Python+environment+variable&quot; name=&quot;How+to+delete+a+Python+environment+variable&quot;&gt;How to delete a Python environment variable&lt;/h3&gt;
&lt;p&gt;If you need to delete a Python environment variable, use the &lt;code&gt;os.environ.pop&lt;/code&gt; function:&lt;/p&gt;
&lt;p&gt;To extend our &lt;code&gt;DB_URL&lt;/code&gt; example above, you may want to delete the other &lt;code&gt;DB_&lt;/code&gt; prefixed
fields to ensure the only way the app can connect to the database is via &lt;code&gt;DB_URL&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;Another example is deleting an environment variable once it is no longer needed:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;auth_api(os.environ[&#039;API_KEY&#039;]) # Use API_KEY
os.environ.pop(&#039;API_KEY&#039;) # Delete API_KEY as it&#039;s no longer needed&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Why+default+values+for+environment+variables+should+be+avoided&quot; name=&quot;Why+default+values+for+environment+variables+should+be+avoided&quot;&gt;Why default values for environment variables should be avoided&lt;/h3&gt;
&lt;p&gt;You might be surprised to learn it&#039;s best to &lt;strong&gt;avoid providing default values&lt;/strong&gt;
as much as possible. Why?&lt;/p&gt;
&lt;p&gt;Default values can make debugging a misconfigured application more difficult, as the
final config values will likely be a combination of hard-coded default values and
environment variables.&lt;/p&gt;
&lt;p&gt;Relying purely on environment variables (or as much as possible) means you have a single
source of truth for how your application was configured, making troubleshooting easier.&lt;/p&gt;
&lt;h3 id=&quot;Using+a+.env+file+for+Python+environment+variables&quot; name=&quot;Using+a+.env+file+for+Python+environment+variables&quot;&gt;Using a .env file for Python environment variables&lt;/h3&gt;
&lt;p&gt;As an application grows in size and complexity, so does the number of environment variables.&lt;/p&gt;
&lt;p&gt;Many projects experience growing pains when using environment variables for app config
and secrets because there is no clear and consistent strategy for how to manage them,
particularly when deploying to multiple environments.&lt;/p&gt;
&lt;p&gt;A simple (but not easily scalable) solution is to use a &lt;code&gt;.env&lt;/code&gt; file to contain all of the
variables for a specific environment.&lt;/p&gt;
&lt;p&gt;Then you would use a Python library such as &lt;a href=&quot;https://github.com/theskumar/python-dotenv&quot;&gt;python-dotenv&lt;/a&gt; to parse the &lt;code&gt;.env&lt;/code&gt; file
and populate the &lt;code&gt;os.environ&lt;/code&gt; object.&lt;/p&gt;
&lt;p&gt;Using &lt;a href=&quot;https://github.com/theskumar/python-dotenv&quot;&gt;python-dotenv&lt;/a&gt;, you can save the below to a file named &lt;code&gt;.env&lt;/code&gt; (note how it&#039;s the
same syntax for setting a variable in the shell):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;API_KEY=&quot;357A70FF-BFAA-4C6A-8289-9831DDFB2D3D&quot;
HOSTNAME=&quot;0.0.0.0&quot;
PORT=&quot;8080&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then save the following to &lt;code&gt;dotenv-test.py&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Rename `os.environ` to `env` for nicer code
from os import environ as env

from dotenv import load_dotenv
load_dotenv()

print(&#039;API_KEY:  {}&#039;.format(env[&#039;API_KEY&#039;]))
print(&#039;HOSTNAME: {}&#039;.format(env[&#039;HOSTNAME&#039;]))
print(&#039;PORT:     {}&#039;.format(env[&#039;PORT&#039;]))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then run &lt;code&gt;dotenv-test.py&lt;/code&gt; to test the environment variables are being populated:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python3 dotenv-test.py
# &amp;gt;&amp;gt; API_KEY:  357A70FF-BFAA-4C6A-8289-9831DDFB2D3D
# &amp;gt;&amp;gt; HOSTNAME: 0.0.0.0
# &amp;gt;&amp;gt; PORT:     8080&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While &lt;code&gt;.env&lt;/code&gt; files are simple and easy to work with at the beginning, they also cause a new
set of problems such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How to keep &lt;code&gt;.env&lt;/code&gt; files in-sync for every developer in their local environment?&lt;/li&gt;
&lt;li&gt;If there is an outage due to misconfiguration, accessing the container or VM directly in
order to view the contents of the .env may be required for troubleshooting.&lt;/li&gt;
&lt;li&gt;How do you generate a &lt;code&gt;.env&lt;/code&gt; file for a CI/CD job such as GitHub Actions without
committing the &lt;code&gt;.env&lt;/code&gt; file to the repository?&lt;/li&gt;
&lt;li&gt;If a mix of environment variables and a &lt;code&gt;.env&lt;/code&gt; file is used, the only way to determine
the final configuration values could be by introspecting the application.&lt;/li&gt;
&lt;li&gt;Onboarding a developer by sharing an unencrypted &lt;code&gt;.env&lt;/code&gt; file with potentially sensitive
data in a chat application such as Slack could pose security issues.&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Z-Wave Associations with Home Assistant</title>
<link href="https://www.0ink.net/posts/2024/2024-12-01-zwave-pairing.html"></link>
<id>urn:uuid:4bbc5391-bccf-ccac-162f-d9a6f6a90775</id>
<updated>2024-11-22T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Introduction
Configuring associations
Setting associations
Removing associations
Last words
...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Introduction&quot;&gt;Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Configuring+associations&quot;&gt;Configuring associations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Setting+associations&quot;&gt;Setting associations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Removing+associations&quot;&gt;Removing associations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Last+words&quot;&gt;Last words&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/zassoc/zwavelogo.png&quot; alt=&quot;z-wave logo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This is an update to &lt;a href=&quot;/posts/2019/2019-03-01-z-wave-associations-with-vera.html&quot;&gt;Z-Wave associations with Vera UI&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;Introduction&quot; name=&quot;Introduction&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Z-Wave associations lets you control &lt;em&gt;slave&lt;/em&gt; devices from a &lt;em&gt;master&lt;/em&gt;
device without the Z-Wave controller or Hub.&lt;/p&gt;
&lt;p&gt;For this to really work the &lt;em&gt;master&lt;/em&gt; device sending commands must
support this functionality.  This varies from device to device,
so you must look up the documentation of the &lt;em&gt;master&lt;/em&gt; device and
find the supported associations groups.  In a nutshell, you look-up
what each association group is for, and then you add to that group
the &lt;em&gt;slave&lt;/em&gt; devices that will receive the Z-wave commands from the
&lt;em&gt;master&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;See &lt;a href=&quot;https://www.vesternet.com/en-eu/pages/z-wave-groups-scenes-associations&quot;&gt;this article&lt;/a&gt; for more information on associations.&lt;/p&gt;
&lt;p&gt;In &lt;a href=&quot;https://www.home-assistant.io/&quot;&gt;Home Assistant&lt;/a&gt;, Z-Wave support is provided by add-ons.  There
are two add-ons available:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/home-assistant/addons/tree/master/zwave_js&quot;&gt;Z-Wave JS&lt;/a&gt; : from the official Add On store&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/hassio-addons/addon-zwave-js-ui&quot;&gt;Z-Wave JS UI&lt;/a&gt; : from the  Community Add Ons&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both are based on the &lt;a href=&quot;https://github.com/zwave-js&quot;&gt;Z-Wave JS&lt;/a&gt; project.  However the &lt;a href=&quot;https://github.com/hassio-addons/addon-zwave-js-ui&quot;&gt;Z-Wave JS UI&lt;/a&gt; Add On from the
Community store, exposes the &lt;a href=&quot;https://github.com/zwave-js&quot;&gt;Z-Wave JS&lt;/a&gt; control panel, where as the official &lt;a href=&quot;https://github.com/home-assistant/addons/tree/master/zwave_js&quot;&gt;Z-Wave JS&lt;/a&gt;
Add on does &lt;strong&gt;NOT&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.home-assistant.io/&quot;&gt;Home assistant&lt;/a&gt; by default does not expose the functionality and UI
interface to modify Z-Wave group associations.  That is why &lt;a href=&quot;https://github.com/hassio-addons/addon-zwave-js-ui&quot;&gt;Z-Wave JS UI&lt;/a&gt; Add On is
required.&lt;/p&gt;
&lt;h2 id=&quot;Configuring+associations&quot; name=&quot;Configuring+associations&quot;&gt;Configuring associations&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/zassoc/addon-lo.png&quot; alt=&quot;addon&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Open the &lt;a href=&quot;https://github.com/hassio-addons/addon-zwave-js-ui&quot;&gt;Z-Wave JS UI&lt;/a&gt; user interface by using the sidebar link (if enabled in the
addon settings page) or from the settings addon page, using the link &lt;strong&gt;OPEN WEB UI&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/zassoc/menu-lo.png&quot; alt=&quot;control panel&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Select control panel from the tab menu.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/zassoc/device-grp-lo.png&quot; alt=&quot;devinfo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Find the &lt;em&gt;master&lt;/em&gt; device from the list of devices, and open its properties.  Click on the &lt;strong&gt;GROUPS&lt;/strong&gt;
tab.&lt;/p&gt;
&lt;h2 id=&quot;Setting+associations&quot; name=&quot;Setting+associations&quot;&gt;Setting associations&lt;/h2&gt;
&lt;p&gt;To create a new association click on the &lt;strong&gt;ADD&lt;/strong&gt; option.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/zassoc/new-lo.png&quot; alt=&quot;new&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Complete the form:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Node Endpoint&lt;/strong&gt; : Simply select from the list.  Usually &lt;strong&gt;Root Endpoint&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Group&lt;/strong&gt; : Select the group.  Usually the group refers to the event that will be used.  For
example, it could be a button group.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Target Node&lt;/strong&gt; : Select from the list the &lt;em&gt;slave&lt;/em&gt; device that we want to control.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Target Endpoint&lt;/strong&gt; : Leave blank unless the &lt;em&gt;slave&lt;/em&gt; device has multiple controls (for example
a double switch).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Removing+associations&quot; name=&quot;Removing+associations&quot;&gt;Removing associations&lt;/h2&gt;
&lt;p&gt;Simply click on the red trashcan icon next to the association you want to delete.&lt;/p&gt;
&lt;h2 id=&quot;Last+words&quot; name=&quot;Last+words&quot;&gt;Last words&lt;/h2&gt;
&lt;p&gt;Note that some associations are always available by default and can not be changed.  These
are used to report the device state to the Z-Wave Controller/Hub.&lt;/p&gt;</content>
</entry>
<entry>
<title>Ansible Snippets</title>
<link href="https://www.0ink.net/posts/2024/2024-11-15-ansible-tips.html"></link>
<id>urn:uuid:2ba92ccd-3e94-4fc1-c172-1034e26d1358</id>
<updated>2024-11-22T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Bootstraping
Execution order
Create a file without external template
Creating inventory script
Writing ansible modules in sh
...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Bootstraping&quot;&gt;Bootstraping&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Execution+order&quot;&gt;Execution order&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Create+a+file+without+external+template&quot;&gt;Create a file without external template&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Creating+inventory+script&quot;&gt;Creating inventory script&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Writing+ansible+modules+in+sh&quot;&gt;Writing ansible modules in sh&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Check+mode&quot;&gt;Check mode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Diff+mode&quot;&gt;Diff mode&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Writing+Ansible+Action+Plugins&quot;&gt;Writing Ansible Action Plugins&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Transferring+data+from+an+ActionPlugin&quot;&gt;Transferring data from an ActionPlugin&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Using+Ansible+playbooks+through+SSH+bastion+hosts%2Fjump+servers&quot;&gt;Using Ansible playbooks through SSH bastion hosts/jump servers&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Inventory+vars&quot;&gt;Inventory vars&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#SSH+config&quot;&gt;SSH config&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#TCP+Tunneling&quot;&gt;TCP Tunneling&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Report+no+changes+when+running+a+script&quot;&gt;Report no changes when running a script&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Adding+check_mode+to+a+script&quot;&gt;Adding check_mode to a script&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Role+File+structure&quot;&gt;Role File structure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Disable+gathering+facts&quot;&gt;Disable gathering facts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#vars+and+defaults+in+roles&quot;&gt;vars and defaults in roles&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Best+practices&quot;&gt;Best practices&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/ansible_logo.png&quot; alt=&quot;ansible logo&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;Bootstraping&quot; name=&quot;Bootstraping&quot;&gt;Bootstraping&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Create a default config file: &lt;br /&gt;&lt;code&gt;ansible-config init --disabled &amp;gt; ansible.cfg&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Createa new ansible role directory: &lt;br /&gt;&lt;code&gt;ansible-galaxy init --init-path roles _role-name_&lt;/code&gt; &lt;br /&gt;This creates the directory &lt;em&gt;role-name&lt;/em&gt; in the &lt;code&gt;roles&lt;/code&gt; directory.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Execution+order&quot; name=&quot;Execution+order&quot;&gt;Execution order&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;pre_tasks&lt;/li&gt;
&lt;li&gt;roles (in the order they are listed)&lt;/li&gt;
&lt;li&gt;tasks&lt;/li&gt;
&lt;li&gt;handlers (only if notified by a task and before post_tasks)&lt;/li&gt;
&lt;li&gt;post_tasks&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;Create+a+file+without+external+template&quot; name=&quot;Create+a+file+without+external+template&quot;&gt;Create a file without external template&lt;/h2&gt;
&lt;p&gt;Normally you can use the &lt;a href=&quot;https://docs.ansible.com/ansible/latest/collections/ansible/builtin/template_module.html&quot;&gt;template module&lt;/a&gt;
to send a Jinja2 templated file to the managed host.  This requires that the
Jinja2 template be stored in its own file.  Some times I find it more convenient
to place the template directly into the YAML file.&lt;/p&gt;
&lt;p&gt;This can be achieved using the &lt;a href=&quot;https://docs.ansible.com/ansible/latest/collections/ansible/builtin/copy_module.html&quot;&gt;copy module&lt;/a&gt;
and the &lt;a href=&quot;https://docs.ansible.com/ansible/latest/collections/ansible/builtin/copy_module.html#parameter-content&quot;&gt;content&lt;/a&gt;
parameter.  Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: Create a customized file
  copy:
    content: |
      Hello {{ who }},
      This file was created by Ansible.
    dest: /path/to/your/file.txt
  vars:
    who: &quot;World&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Creating+inventory+script&quot; name=&quot;Creating+inventory+script&quot;&gt;Creating inventory script&lt;/h2&gt;
&lt;p&gt;When specifying inventories in ansible, an executable file can be specified.
This will be executed to generate the inventory.  It takes the following
command line arguments:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;script --list&lt;/code&gt; &lt;br /&gt;Returns a JSON formatted inventory.  Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
    &quot;group001&quot;: {
        &quot;hosts&quot;: [&quot;host001&quot;, &quot;host002&quot;],
        &quot;vars&quot;: {
            &quot;var1&quot;: true
        },
        &quot;children&quot;: [&quot;group002&quot;]
    },
    &quot;group002&quot;: {
        &quot;hosts&quot;: [&quot;host003&quot;,&quot;host004&quot;],
        &quot;vars&quot;: {
            &quot;var2&quot;: 500
        },
        &quot;children&quot;:[]
    }
    &quot;_meta&quot;: {
        &quot;hostvars&quot;: {
            &quot;host001&quot;: {
                &quot;var001&quot; : &quot;value&quot;
            },
            &quot;host002&quot;: {
                &quot;var002&quot;: &quot;value&quot;
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output should be a JSON object containing all the groups to be managed as
dictionary items.  Each group&#039;s value should be either an object containing a
list of each host, any child groiups, and potential group variables, or simply
a list of hosts.  Empty elements of a group can be omitted. &lt;br /&gt;An optional &lt;code&gt;_meta&lt;/code&gt; block can be added to contain host specific variables.  If
this is omitted, the &lt;code&gt;--host&lt;/code&gt; command line argument is used to query host variables.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;script --host&lt;/code&gt; &lt;em&gt;hostname&lt;/em&gt; &lt;br /&gt;Where &lt;em&gt;hostname&lt;/em&gt; is a host from the &lt;code&gt;--list&lt;/code&gt;.  The script should return
either an empty JSON object, or a JSON dictionary containing meta varaibles specific
to the host.  Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
    &quot;VAR001&quot;: &quot;VALUE&quot;,
    &quot;VAR002&quot;: &quot;VALUE&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Other arguments are allowed but &lt;a href=&quot;https://www.ansible.com/&quot;&gt;ansible&lt;/a&gt; will not use them.&lt;/p&gt;
&lt;p&gt;See &lt;a href=&quot;https://docs.ansible.com/ansible/latest/dev_guide/developing_inventory.html#developing-inventory-scripts&quot;&gt;Inventory scripts&lt;/a&gt;
for more details.&lt;/p&gt;
&lt;p&gt;You can use the &lt;a href=&quot;https://docs.ansible.com/ansible/latest/cli/ansible-inventory.html#ansible-inventory&quot;&gt;ansible-inventory&lt;/a&gt;
command to see what &lt;a href=&quot;https://www.ansible.com/&quot;&gt;ansible&lt;/a&gt; will process for its inventory.&lt;/p&gt;
&lt;h2 id=&quot;Writing+ansible+modules+in+sh&quot; name=&quot;Writing+ansible+modules+in+sh&quot;&gt;Writing ansible modules in sh&lt;/h2&gt;
&lt;p&gt;See &lt;a href=&quot;https://docs.ansible.com/ansible/latest/dev_guide/developing_program_flow_modules.html&quot;&gt;Ansible Module architecture&lt;/a&gt;
for more details.&lt;/p&gt;
&lt;p&gt;This documents how to create &lt;a href=&quot;https://docs.ansible.com/ansible/latest/dev_guide/developing_program_flow_modules.html&quot;&gt;Old-style&lt;/a&gt;
Ansible module.  These are less efficient but are asier to implement in shell script.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.ansible.com/&quot;&gt;Ansible&lt;/a&gt; playbooks are meant to be declarative in nature.  So for handling more
complex tasks the recommendation is to write modules, which then can be used from a
playbook.  To create a module in shell script, you just need to create a file in your
module&#039;s path (ANSIBLE_LIBRARY).   Input parameters are given as a file in &amp;quot;$1&amp;quot;.  This
file is formatted as a &lt;code&gt;source&lt;/code&gt;able file so using:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;. &quot;$1&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The return code of the script is used to determine if the module was succesful or
an error happened.&lt;/p&gt;
&lt;p&gt;The output of the script &lt;em&gt;must&lt;/em&gt; be in JSON format.  And it should contain the following
keys:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;changed&lt;/code&gt; : boolean indicating if changes were made&lt;/li&gt;
&lt;li&gt;&lt;code&gt;msg&lt;/code&gt; : optional informational message, particularly useful in an error condition.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ansible_facts&lt;/code&gt; : dictionary containing facts that will be added to the playbook run.
This is optional.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are additional arguments sent to the script.  These are &lt;a href=&quot;https://docs.ansible.com/ansible/latest/dev_guide/developing_program_flow_modules.html#internal-arguments&quot;&gt;internal ansible&lt;/a&gt;
arguments.  Worth mentioning are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;_ansible_no_log&lt;/code&gt; (boolean) : do not log output.  Used to keep sensitive strings from logging.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_ansible_debug&lt;/code&gt; (boolean) : debugging&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_ansible_diff&lt;/code&gt; (boolean) : Running in diff mode.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_ansible_check_mode&lt;/code&gt; (boolean) : Running in check mode.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Check+mode&quot; name=&quot;Check+mode&quot;&gt;Check mode&lt;/h3&gt;
&lt;p&gt;If &lt;code&gt;_ansible_check_mode&lt;/code&gt; is set to &lt;code&gt;True&lt;/code&gt;, the user is running the playbook with the
&lt;code&gt;--check&lt;/code&gt; flag.  Modules should only show that changes would be made, without making
any actual changes.&lt;/p&gt;
&lt;p&gt;More details &lt;a href=&quot;https://medium.com/opsops/understanding-ansibles-check-mode-299fd8a6a532&quot;&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;Diff+mode&quot; name=&quot;Diff+mode&quot;&gt;Diff mode&lt;/h3&gt;
&lt;p&gt;If &lt;code&gt;_ansible_diff&lt;/code&gt; is set to &lt;code&gt;True&lt;/code&gt;, the user is running the playbook with the &lt;code&gt;--diff&lt;/code&gt;
flag.  Modules that support this should add a key named &lt;code&gt;diff&lt;/code&gt; with either:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;two keys, &lt;code&gt;before&lt;/code&gt; and &lt;code&gt;after&lt;/code&gt;.  This contains the contents before and after the change.&lt;/li&gt;
&lt;li&gt;single &lt;code&gt;prepared&lt;/code&gt; key.  This contains a list with a textual descriptions of the changes
to be made.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;See &lt;a href=&quot;https://blog.devops.dev/writing-ansible-modules-with-support-for-diff-mode-cae70de1c25f&quot;&gt;article&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;Writing+Ansible+Action+Plugins&quot; name=&quot;Writing+Ansible+Action+Plugins&quot;&gt;Writing Ansible Action Plugins&lt;/h2&gt;
&lt;p&gt;Modules described earlier, run on the managed node.  While they can return data to the control
node, sometimes is necessary to do some preparatory work on the control node before
calling a Module proper.  This is done using &lt;a href=&quot;https://docs.ansible.com/ansible/latest/dev_guide/developing_plugins.html#action-plugins&quot;&gt;Action Plugins&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Essentially, action plugins let you integrate local processing and local data with module functionality.&lt;/p&gt;
&lt;p&gt;To create an action plugin, create a new class with the Base(ActionBase) class as the parent:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from ansible.plugins.action import ActionBase

class ActionModule(ActionBase):
    pass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From there, execute the module using the &lt;code&gt;_execute_module&lt;/code&gt; method to call the original module.
After successful execution of the module, you can modify the module return data.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;module_return = self._execute_module(module_name=&#039;&amp;lt;NAME_OF_MODULE&amp;gt;&#039;,
                                     module_args=module_args,
                                     task_vars=task_vars, tmp=tmp)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A simple example template:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;#!/usr/bin/python
# Make coding more python3-ish, this is required for contributions to Ansible
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

from ansible.plugins.action import ActionBase
from datetime import datetime

class ActionModule(ActionBase):
    def run(self, tmp=None, task_vars=None):
        result = super(ActionModule, self).run(tmp, task_vars)
        module_args = self._task.args.copy()
        module_return = self._execute_module(module_name=&#039;setup&#039;,
                                             module_args=module_args,
                                             task_vars=task_vars, tmp=tmp)
        result.update(module_return)
        return result&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Transferring+data+from+an+ActionPlugin&quot; name=&quot;Transferring+data+from+an+ActionPlugin&quot;&gt;Transferring data from an ActionPlugin&lt;/h3&gt;
&lt;p&gt;So you need to copy a large file from the control node to the managed node
in an Action Plugin.  To do this you need to declar in your class:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;TRANSFERS_FILES = True&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next in your Action implementation, you can use this command to create
temporary file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;tmp_src = self._connection._shell.join_path(self._connection._shell.tmpdir, &#039;archive.zip&#039;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then you can copy bytes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;self._transfer_data(tmp_src, bytes_object)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or copy a file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;self._transfer_file(local_file, tmp_src)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A complete example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;#!/usr/bin/python

# Make coding more python3-ish, this is required for contributions to Ansible
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

from ansible.plugins.action import ActionBase
from ansible.errors import AnsibleActionFail, AnsibleError

class ActionModule(ActionBase):
  TRANSFERS_FILES = True

  def run(self, tmp=None, task_vars=None):
    result = super(ActionModule, self).run(tmp, task_vars)

    # Get the arguments passed to the action plugin
    src = self._task.args.get(&#039;src&#039;, &#039;&#039;)
    if len(src) == 0:
      raise AnsibleActionFail(&#039;Missing or empty src parameter&#039;,result=result)

    # Copy archive to remote/managed node:
    try:
      tmp_src = self._connection._shell.join_path(self._connection._shell.tmpdir, &#039;archive.zip&#039;)
      self._transfer_file(src, tmp_src)
    except AnsibleError as e:
      raise AnsibleActionFail(to_text(e))

    return result
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Using+Ansible+playbooks+through+SSH+bastion+hosts%2Fjump+servers&quot; name=&quot;Using+Ansible+playbooks+through+SSH+bastion+hosts%2Fjump+servers&quot;&gt;Using Ansible playbooks through SSH bastion hosts/jump servers&lt;/h2&gt;
&lt;p&gt;There are two approaches:&lt;/p&gt;
&lt;h3 id=&quot;Inventory+vars&quot; name=&quot;Inventory+vars&quot;&gt;Inventory vars&lt;/h3&gt;
&lt;p&gt;The first way to do it with Ansible is to describe how to connect through the proxy server
in Ansible&#039;s inventory. This is helpful for a project that might be run from various workstations
or servers without the same SSH configuration (the configuration is stored alongside the
playbook, in the inventory).&lt;/p&gt;
&lt;p&gt;Example Inventory:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;[proxy]
bastion.example.com

[nodes]
private-server-1.example.com
private-server-2.example.com
private-server-3.example.com

[nodes:vars]
ansible_ssh_common_args=&#039;-o ProxyCommand=&quot;ssh -p 2222 -W %h:%p -q username@bastion.example.com&quot;&#039;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This sets up an SSH proxy through bastion.example.com on port 2222 (if using the default port,
22, you can drop the port argument). The -W argument tells SSH it can forward stdin and stdout
through the host and port, effectively allowing Ansible to manage the node behind the
bastion/jump server.&lt;/p&gt;
&lt;p&gt;The important config line is &lt;code&gt;ansible_ssh_common_args&lt;/code&gt;, which adds the relevant options to
the ansible &lt;code&gt;ssh&lt;/code&gt; command.  A few notes on the options:&lt;/p&gt;
&lt;p&gt;Recent SSH versions could just use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;ansible_ssh_common_args=&#039;-J username@bastion.example.com:2222&quot;&#039;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;SSH+config&quot; name=&quot;SSH+config&quot;&gt;SSH config&lt;/h3&gt;
&lt;p&gt;The alternative, which would apply the proxy configuration to all SSH connections on a
given workstation, is to add the following configuration inside your &lt;code&gt;~/.ssh/config&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;
Host private-server-*.example.com
   ProxyJump user@bastion:2222&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ansible will automatically use whatever SSH options are defined in the user or global SSH
config, so it should pick these settings up even if you don&#039;t modify your inventory.&lt;/p&gt;
&lt;p&gt;This method is most helpful if you know your playbook will always be run from a server or
workstation where the SSH config is present.  Also, this applies to normal &lt;code&gt;ssh&lt;/code&gt; invokations
so if you also use the &lt;code&gt;ssh&lt;/code&gt; and related utilities directly, then, htey will use the
same configuration.&lt;/p&gt;
&lt;h3 id=&quot;TCP+Tunneling&quot; name=&quot;TCP+Tunneling&quot;&gt;TCP Tunneling&lt;/h3&gt;
&lt;p&gt;These options assume that the bastion host has TCP Tunneling/Forwarding enabled.  If your
bastion host has this feature &lt;strong&gt;disabled&lt;/strong&gt;, you can replace the &lt;code&gt;ProxyJump&lt;/code&gt; with
proxy command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;  ProxyCommand ssh user@bastion nc %h %p&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This replaces the &lt;code&gt;-W&lt;/code&gt; option with the &lt;code&gt;nc&lt;/code&gt; (netcat) command.&lt;/p&gt;
&lt;p&gt;Source: &lt;a href=&quot;https://www.jeffgeerling.com/blog/2022/using-ansible-playbook-ssh-bastion-jump-host&quot;&gt;https://www.jeffgeerling.com/blog/2022/using-ansible-playbook-ssh-bastion-jump-host&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;Report+no+changes+when+running+a+script&quot; name=&quot;Report+no+changes+when+running+a+script&quot;&gt;Report no changes when running a script&lt;/h2&gt;
&lt;p&gt;Running a script always assume that it makes changes.&lt;/p&gt;
&lt;p&gt;Using scripts is &lt;em&gt;not recommenteded&lt;/em&gt; because it is just as easy to convert the script into
a proper &lt;a href=&quot;https://www.ansible.com/&quot;&gt;Ansible&lt;/a&gt; module.  Doing so makes it also possible to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Return ansible facts&lt;/li&gt;
&lt;li&gt;Report change status&lt;/li&gt;
&lt;li&gt;Support &lt;strong&gt;check&lt;/strong&gt; and &lt;strong&gt;diff&lt;/strong&gt; modes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Regardless, this is an example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;tasks:

- name: Exec sh command
  shell:
    cmd: &quot;echo &#039;&#039;; exit 254;&quot;
  register: result
  failed_when: result.rc != 0 and result.rc != 254
  changed_when: result.rc != 254&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I have customized &lt;code&gt;command&lt;/code&gt; module and the &lt;code&gt;script&lt;/code&gt; action plugin to simplify these
three lines of code into a single line.  So the previous example becomes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;tasks:

- name: Exec sh command
  shell:
    cmd: &quot;echo &#039;&#039;; exit 254;&quot;
  no_change_rc: 254&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Adding+check_mode+to+a+script&quot; name=&quot;Adding+check_mode+to+a+script&quot;&gt;Adding check_mode to a script&lt;/h3&gt;
&lt;p&gt;In addition, it is actually possible to support &lt;code&gt;check_mode&lt;/code&gt; in a script.  You need to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pass the appropriate settings to your script indicating to it that it is in &lt;code&gt;check_mode&lt;/code&gt;. &lt;br /&gt;This can be done by adding a check for the &lt;code&gt;ansible_check_mode&lt;/code&gt; variable in a Jinja2 template.&lt;/li&gt;
&lt;li&gt;Set-up the task so that it will also execute in &lt;code&gt;check_mode&lt;/code&gt; by adding the tag: &lt;code&gt;check_mode: false&lt;/code&gt;
to it.&lt;/li&gt;
&lt;li&gt;Example:
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: &quot;Apply config to &lt;img src=&quot;NacoWiki/web/albatros.php/posts/2024/domu_name&quot; alt=&quot;domu_name&quot; title=&quot;domu_name&quot;&gt;&quot;
  command: |
    xop cfg --no-change-rc=127 {% if ansible_check_mode %}--dry-run{% endif %} &lt;img src=&quot;NacoWiki/web/albatros.php/posts/2024/domu_name&quot; alt=&quot;domu_name&quot; title=&quot;domu_name&quot;&gt;
  register: res
  failed_when: res.rc != 0 and res.rc != 127
  changed_when: res.rc != 127
  check_mode: false&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;In this example, the &lt;code&gt;xop cfg&lt;/code&gt; command gets executed regardless if &lt;code&gt;check_mode&lt;/code&gt; is on or off.&lt;/li&gt;
&lt;li&gt;The script then is passed the &lt;code&gt;--dry-run&lt;/code&gt; option if &lt;code&gt;ansible_check_mode&lt;/code&gt; is on.  So the script
will not make any actual changes to the system.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Scripts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/snippets/2024/ansible/action_plugins/script.py&quot;&gt;Custom script action&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/snippets/2024/ansible/modules/command.py&quot;&gt;Custom command module&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Role+File+structure&quot; name=&quot;Role+File+structure&quot;&gt;Role File structure&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;roles
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;role name&lt;/em&gt;
&lt;ul&gt;
&lt;li&gt;tasks
&lt;ul&gt;
&lt;li&gt;main.yml : tasks file can include smaller files if warranted&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;handlers
&lt;ul&gt;
&lt;li&gt;main.yml : handlers file&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;templates : files to use in the template resource
&lt;ul&gt;
&lt;li&gt;ntp.conf.j2 : templates end in &lt;code&gt;.j2&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;files
&lt;ul&gt;
&lt;li&gt;bar.txt : files for use with the copy resource&lt;/li&gt;
&lt;li&gt;foo.sh : script files for use with the script resource&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;vars
&lt;ul&gt;
&lt;li&gt;main.yml : variables associated with this role&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;defaults
&lt;ul&gt;
&lt;li&gt;main.yml : default lower priority variables for this role&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;meta
&lt;ul&gt;
&lt;li&gt;main.yml : role dependencies&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;library : roles can also include custom modules&lt;/li&gt;
&lt;li&gt;module_utils : roles can also include custom module_utils&lt;/li&gt;
&lt;li&gt;lookup_plugins : or other types of plugins, like lookup in this case&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See: &lt;a href=&quot;https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_reuse_roles.html&quot;&gt;https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_reuse_roles.html&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;Disable+gathering+facts&quot; name=&quot;Disable+gathering+facts&quot;&gt;Disable gathering facts&lt;/h2&gt;
&lt;p&gt;This needed for managing hosts that do not have a python interpreter by default.
Specially for scenarios where it is needed to bootstrap the python interpreter.&lt;/p&gt;
&lt;p&gt;There are different ways to do this.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Disabling on a per playbook basis. &lt;br /&gt;Example:
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# sample playbook
- hosts: sites
  user: root
  tags:
  - configuration
  gather_facts: no
  tasks:
  - ....
  # ... tasks to install python ...
  - ...
  # Optionally, call setup explicitly
  - setup
  # ... more tasks ...&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Using an environment variable &lt;br /&gt;&lt;code&gt;ANSIBLE_GATHERING&lt;/code&gt; &lt;br /&gt;This can be set to &lt;code&gt;explicit&lt;/code&gt;.  For example: &lt;br /&gt;&lt;code&gt;ANSIBLE_GATHERING=0 ansible-playbook playbook.yaml -i inventory.yaml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;From &lt;code&gt;ansible.cfg&lt;/code&gt;:
&lt;ul&gt;
&lt;li&gt;plays will gather facts by default, which contain information about
the remote system.&lt;/li&gt;
&lt;li&gt;Options:
&lt;ul&gt;
&lt;li&gt;smart - gather by default, but don&#039;t regather if already gathered&lt;/li&gt;
&lt;li&gt;implicit - gather by default, turn off with gather_facts: False&lt;/li&gt;
&lt;li&gt;explicit - do not gather by default, must say gather_facts: True&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;example:
&lt;pre&gt;&lt;code&gt;gathering = explicit&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;vars+and+defaults+in+roles&quot; name=&quot;vars+and+defaults+in+roles&quot;&gt;vars and defaults in roles&lt;/h2&gt;
&lt;p&gt;In an &lt;a href=&quot;https://www.ansible.com/&quot;&gt;ansible&lt;/a&gt; role, there are two places where you can define variables,
&lt;code&gt;defaults&lt;/code&gt; and &lt;code&gt;vars&lt;/code&gt; directories.  It always was confusing to me when
would you use one over the other one.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;defaults&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Contains default variables for the role.&lt;/li&gt;
&lt;li&gt;Variables in this directory have the lowest precedence, meaning they can be
easily overridden by other variable sources (e.g., playbooks, command line).&lt;/li&gt;
&lt;li&gt;Use this for variables that can be set by users or other roles but need to be
initialized with a default value.&lt;/li&gt;
&lt;li&gt;Example:
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# defaults/main.yml
my_default_variable: &quot;default_value&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;vars&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Contains variables that are generally more static and not meant to be easily
overridden.&lt;/li&gt;
&lt;li&gt;Variables in this directory have higher precedence than those in &lt;code&gt;defaults&lt;/code&gt;
but lower than those defined in inventory files or extra vars.&lt;/li&gt;
&lt;li&gt;Use this for essential role-specific variables that should rarely change.&lt;/li&gt;
&lt;li&gt;Example:
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# vars/main.yml
my_static_variable: &quot;static_value&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;Best+practices&quot; name=&quot;Best+practices&quot;&gt;Best practices&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Use&lt;/strong&gt; &lt;code&gt;defaults/&lt;/code&gt; for configurable options: Place variables here if you expect
them to be overridden by the user or other roles.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use&lt;/strong&gt; &lt;code&gt;vars/&lt;/code&gt; for crucial settings: Use this directory for variables that are
critical to the role and not intended to be changed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Document Variables:&lt;/strong&gt; Always document the purpose and acceptable values for
your variables, especially in &lt;code&gt;defaults/&lt;/code&gt; since users might change them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keep it Simple:&lt;/strong&gt; Avoid overly complex variable hierarchies. Keep your
role&#039;s variables understandable and maintainable.&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Modifying VM configuration with libvirtd</title>
<link href="https://www.0ink.net/posts/2024/2024-11-01-virtxml.html"></link>
<id>urn:uuid:796ad385-6543-e604-13b6-48ce4170772a</id>
<updated>2024-11-22T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
The other day, I had to update a VM configuration managed via libvirt
from the command line.  There are different ways to do this.  The easiest probably
is to use the virt-manager application and use the GUI to modify things.

... virt-manager screenshot ...
...]]></summary>
<content type="html">&lt;p&gt;&lt;img src=&quot;/images/2024/virtualization.png&quot; alt=&quot;virt&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The other day, I had to update a VM configuration managed via &lt;a href=&quot;https://libvirt.org/&quot;&gt;libvirt&lt;/a&gt;
from the command line.  There are different ways to do this.  The easiest probably
is to use the &lt;code&gt;virt-manager&lt;/code&gt; application and use the GUI to modify things.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/vmm-details-lo.png&quot; alt=&quot;virt-manager details&quot; /&gt;&lt;/p&gt;
&lt;p&gt;... virt-manager screenshot ...&lt;/p&gt;
&lt;p&gt;Alternatively you can use &lt;code&gt;virsh&lt;/code&gt; with &lt;code&gt;dumpxml&lt;/code&gt;+ &lt;code&gt;undefine&lt;/code&gt; + &lt;code&gt;define&lt;/code&gt; or &lt;code&gt;edit&lt;/code&gt;
to modify the XML definition directly.  While straight forward this is more diffiecult.&lt;/p&gt;
&lt;p&gt;A good in-between is to use the &lt;code&gt;virt-xml&lt;/code&gt; command, which lets you add/remove devices
directly.  See &lt;a href=&quot;https://manpages.ubuntu.com/manpages/xenial/man1/virt-xml.1.html&quot;&gt;man page&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;Examples&quot; name=&quot;Examples&quot;&gt;Examples&lt;/h3&gt;
&lt;p&gt;Adding a network interface:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;virt-xml vmname --add-device --network bridge=br-ens13f3,model=virtio&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Removing sda definition:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-virsh&quot;&gt;virt-xml vmname --remove-device --disk target=sda&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Adding sda definition as an iscsi:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;virt-xml vmname --add-device --disk target=sda,driver.type=raw,source.protocol=iscsi,source.name=iqn.2023-10.world.srv:dlp.target01/1,source.host.name=192.168.39.184,source.host.port=3260,target.bus=sata&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that this can not specify iSCSI initiator IQN&lt;/p&gt;
&lt;p&gt;Adding a serial console:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;virt-xml vmname --add-device --serial pty,target.port=0&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This functionality can be access programmatically from python directly (not via a command)
using:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import os
import sys
sys.path.insert(0, &#039;/usr/share/virt-manager&#039;)
from virtinst import virtxml&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unfortunately there is no API documentation for this so this needs to be read from the
&lt;a href=&quot;https://github.com/virt-manager/virt-manager/blob/main/virtinst/virtxml.py&quot;&gt;source&lt;/a&gt;.&lt;/p&gt;</content>
</entry>
<entry>
<title>Comments enabled</title>
<link href="https://www.0ink.net/posts/2024/2024-10-24-utterances.html"></link>
<id>urn:uuid:afa45148-ac73-247b-c2df-60eac76f9638</id>
<updated>2024-11-22T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[Today I enabled comments on this blog.
You need to have an github account in order to make comment.  This is because the
main audience for this blog is developers and this should cut down on the spam.
Comments are stored GitHub Issues using the Utterances comments widget.
I chose Utterances because:

...]]></summary>
<content type="html">&lt;p&gt;Today I enabled comments on this blog.&lt;/p&gt;
&lt;p&gt;You need to have an &lt;a href=&quot;https://github.com/&quot;&gt;github&lt;/a&gt; account in order to make comment.  This is because the
main audience for this blog is developers and this should cut down on the spam.&lt;/p&gt;
&lt;p&gt;Comments are stored &lt;a href=&quot;https://github.com/features/issues&quot;&gt;GitHub Issues&lt;/a&gt; using the &lt;a href=&quot;https://utteranc.es/&quot;&gt;Utterances&lt;/a&gt; comments widget.&lt;/p&gt;
&lt;p&gt;I chose &lt;a href=&quot;https://utteranc.es/&quot;&gt;Utterances&lt;/a&gt; because:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;it is lightweight&lt;/li&gt;
&lt;li&gt;makes use of the &lt;code&gt;issues&lt;/code&gt; API, which at the moment should be more stable than the
&lt;code&gt;discussions&lt;/code&gt; API.&lt;/li&gt;
&lt;li&gt;Has its own bot, so there is no need to embed javascript with an app ID/secret.&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Tweaking HDMI dummies</title>
<link href="https://www.0ink.net/posts/2024/2024-10-15-hdmi-dummy.html"></link>
<id>urn:uuid:1352ca8c-c0bf-d5a5-b3b6-8397787177e6</id>
<updated>2024-11-22T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Prepare EDID blob
Modify Kernel command line
Update initramfs
Reboot
Post boot EDID
...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Prepare+EDID+blob&quot;&gt;Prepare EDID blob&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Modify+Kernel+command+line&quot;&gt;Modify Kernel command line&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Update+initramfs&quot;&gt;Update initramfs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Reboot&quot;&gt;Reboot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Post+boot+EDID&quot;&gt;Post boot EDID&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/dummy.png&quot; alt=&quot;dummy&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This is a follow-up to my post about &lt;a href=&quot;/posts/2021/2021-02-28-hotplug-hdmi.html&quot;&gt;HDMI hotplug&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Back then I wrote it to handle a KVM switch and how it would deal with switching
monitors and how Linux handled when the monitor was &lt;em&gt;not&lt;/em&gt; plugged in.&lt;/p&gt;
&lt;p&gt;Since then I discovered &lt;a href=&quot;https://en-wiki.ikoula.com/en/HDMI_display_emulator&quot;&gt;HDMI dummy plugs&lt;/a&gt;.  These are small adapters that
connect to an HDMI and fool the computer into thinking there is a monitor still
connected.&lt;/p&gt;
&lt;p&gt;Using it is really a matter of preference.&lt;/p&gt;
&lt;p&gt;On my desk, I have a KVM connecting a Linux desktop and a Windows laptop.  When the KVM
connects to the Windows laptop I would configure the system to extend the desktop to
two monitors.  When I switch from the Windows laptop to my Linux desktop with the
KVM switch, the Windows Operating system detects that the monitor is disconnected
and moves all the opened windows from the external monitor (which is now disconnected)
to the Laptop display.  While this is the &lt;em&gt;common sense&lt;/em&gt; thing to do, I don&#039;t find
the experience pleasent.&lt;/p&gt;
&lt;p&gt;By using the &lt;a href=&quot;https://en-wiki.ikoula.com/en/HDMI_display_emulator&quot;&gt;HDMI dummy plug&lt;/a&gt; adaptor, Windows thinks that the monitor
still is connected while the KVM switches to the other computer.  So when
I switch back, all the windows stay put.&lt;/p&gt;
&lt;p&gt;So the issues that I was having back as described in &lt;a href=&quot;/posts/2021/2021-02-28-hotplug-hdmi.html&quot;&gt;HDMI hotplug&lt;/a&gt;
would not happen as the monitor looks like is always connected.&lt;/p&gt;
&lt;p&gt;Furthermore, looking at the state of software under Linux, the Operating System
already handles monitor hotplugs gracefully.  Similarly, Desktop Environments
work without much issues with monitors being plugged or unplugged on the fly.
Also in &lt;a href=&quot;/posts/2021/2021-02-28-hotplug-hdmi.html&quot;&gt;HDMI hotplug&lt;/a&gt; I was using the
&lt;a href=&quot;https://en.wikipedia.org/wiki/XDM_(display_manager)&quot;&gt;XDM (Display Manager)&lt;/a&gt; which was initially written back 1988, and
is one of the most rudimentary display managers.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/xdm.png&quot; alt=&quot;XDM (Display Manager)&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Of course things in life are never simple.  So this is what happened.&lt;/p&gt;
&lt;p&gt;When I first started using &lt;a href=&quot;https://en-wiki.ikoula.com/en/HDMI_display_emulator&quot;&gt;HDMI dummies&lt;/a&gt; I had a 1080p FHD monitor and 1080p
dummies.  Recently I upgraded my main monitor to a QHD monitor.  So the 1080p dummy
was not able to reach the QHD (1440p resoluation).  Of course, there is no
1440p dummy, so I bought a 4K HDMI dummy.  Which made things a little bit challenging
because the cheapo 4K HDMI dummy would insist to the computer that the preferred
resolution was 4K, but my monitor would not be able to display that.&lt;/p&gt;
&lt;p&gt;Normally monitors use &lt;a href=&quot;https://en.wikipedia.org/wiki/Extended_Display_Identification_Data&quot;&gt;EDID&lt;/a&gt; data to tell the computer what are the preferred
resolutions.  A good &lt;a href=&quot;https://en-wiki.ikoula.com/en/HDMI_display_emulator&quot;&gt;HDMI dummy&lt;/a&gt; would then cache the &lt;a href=&quot;https://en.wikipedia.org/wiki/Extended_Display_Identification_Data&quot;&gt;EDID&lt;/a&gt; information
from the monitor and only present a compatible &lt;a href=&quot;https://en.wikipedia.org/wiki/Extended_Display_Identification_Data&quot;&gt;EDID&lt;/a&gt; to the computer.  For some
reason this did not work.&lt;/p&gt;
&lt;p&gt;Luckly Linux allows to force &lt;a href=&quot;https://en.wikipedia.org/wiki/Extended_Display_Identification_Data&quot;&gt;EDID&lt;/a&gt; to be overriden.  To do that this is
what i had to do.&lt;/p&gt;
&lt;h2 id=&quot;Prepare+EDID+blob&quot; name=&quot;Prepare+EDID+blob&quot;&gt;Prepare EDID blob&lt;/h2&gt;
&lt;p&gt;First plug in the monitor directy &lt;em&gt;without&lt;/em&gt; the &lt;a href=&quot;https://en-wiki.ikoula.com/en/HDMI_display_emulator&quot;&gt;HDMI dummy&lt;/a&gt;.  Determine
the connection in use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;for p in /sys/class/drm/*/status; do con=${p%/status}; echo -n &quot;${con#*/card?-}: &quot;; cat $p; done&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read the &lt;a href=&quot;https://en.wikipedia.org/wiki/Extended_Display_Identification_Data&quot;&gt;EDID&lt;/a&gt; blob from &lt;code&gt;/sys/class/drm/card0{connection}/edid&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cat &amp;lt; /sys/class/drm/card0-HDMI-A-1/edid` &amp;gt; monitor.bin&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use the command &lt;a href=&quot;https://manpages.debian.org/wheezy/read-edid/parse-edid.1&quot;&gt;parse-edid&lt;/a&gt; to read the blob and make sure it
contains valid data.&lt;/p&gt;
&lt;p&gt;Save it to &lt;code&gt;/usr/lib/firmware/edid&lt;/code&gt;.  I would save it to a file with the
monitor model name and &lt;code&gt;bin&lt;/code&gt; extension.&lt;/p&gt;
&lt;h2 id=&quot;Modify+Kernel+command+line&quot; name=&quot;Modify+Kernel+command+line&quot;&gt;Modify Kernel command line&lt;/h2&gt;
&lt;p&gt;Add the following option to the Linux kernel command line:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;drm.edid_firmware=HDMI-A-1:edid/PHL325B1L.bin&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Change &lt;code&gt;HDMI-A-1&lt;/code&gt; with the right connector as seen using a previous command.
Replace &lt;code&gt;PHL325B1L.bin&lt;/code&gt; with the name of the &lt;a href=&quot;https://en.wikipedia.org/wiki/Extended_Display_Identification_Data&quot;&gt;EDID&lt;/a&gt; blob you created earlier.&lt;/p&gt;
&lt;h2 id=&quot;Update+initramfs&quot; name=&quot;Update+initramfs&quot;&gt;Update initramfs&lt;/h2&gt;
&lt;p&gt;Under &lt;a href=&quot;https://voidlinux.org/&quot;&gt;voidlinux&lt;/a&gt;, the &lt;a href=&quot;https://man7.org/linux/man-pages/man8/dracut.8.html&quot;&gt;dracut&lt;/a&gt; utility is used to generate the
initramfs.&lt;/p&gt;
&lt;p&gt;Create a file in &lt;code&gt;/etc/dracut.conf.d/&lt;/code&gt; with the configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# /etc/dracut.conf.d/50-fw.conf 
install_items+=&quot; /usr/lib/firmware/edid/PHL325B1L.bin &quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And generate the new initramfs:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;dracut --force&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Reboot&quot; name=&quot;Reboot&quot;&gt;Reboot&lt;/h2&gt;
&lt;p&gt;At this point all is ready.  Shutdown the computer, plug the monitor
using the &lt;a href=&quot;https://en-wiki.ikoula.com/en/HDMI_display_emulator&quot;&gt;HDMI dummy&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;Post+boot+EDID&quot; name=&quot;Post+boot+EDID&quot;&gt;Post boot EDID&lt;/h2&gt;
&lt;p&gt;Alternatively, you could use this script to force EDID after boot:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/sh
#
# Script to force EDID settings
#
set -euf -o pipefail

#
# Customize these settings
#
EDID_BIN=/usr/lib/edid/PHL325B1L.bin
output=HDMI-A-1
rows=45
cols=160

# Use parse-edid to verify
# save /sys/class/drm/card*/edid
debugfs=/sys/kernel/debug

if [ ! -d $debugfs/dri ] ; then
  umount=true
  mount -t debugfs debugfs $debugfs
  trap &quot;umount $debugfs&quot; EXIT
fi

dd if=$EDID_BIN of=$debugfs/dri/0/$output/edid_override
echo 1 | dd of=$debugfs/dri/0/$output/trigger_hotplug

for n in $(seq 1 6)
do
(
  tput sc
  printf &#039;\033[1;&#039;$rows&#039;r&#039;
  tput rc
  stty rows $rows cols $cols
) &amp;lt; /dev/tty$n &amp;gt;/dev/tty$n 2&amp;gt;&amp;amp;1
done&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;References:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://wiki.archlinux.org/title/kernel_mode_setting#Forcing_modes_and_EDID&quot;&gt;https://wiki.archlinux.org/title/kernel_mode_setting#Forcing_modes_and_EDID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://linuxlink.timesys.com/docs/wiki/engineering/HOWTO_Use_debugfs&quot;&gt;https://linuxlink.timesys.com/docs/wiki/engineering/HOWTO_Use_debugfs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Docker in Docker</title>
<link href="https://www.0ink.net/posts/2024/2024-10-01-docker-in-docker.html"></link>
<id>urn:uuid:d54687cc-264a-f8ae-2ce2-745436defab1</id>
<updated>2024-11-22T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Introduction
Method 1: Mounting /var/run/docker.sock

docker.sock permission error

...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Introduction&quot;&gt;Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Method+1%3A+Mounting+%2Fvar%2Frun%2Fdocker.sock&quot;&gt;Method 1: Mounting /var/run/docker.sock&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#docker.sock+permission+error&quot;&gt;docker.sock permission error&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Method+2%3A+Docker+in+Docker+Using+DinD&quot;&gt;Method 2: Docker in Docker Using DinD&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Method+3%3A+Docker+in+Docker+Using+Sysbox+Runtime&quot;&gt;Method 3: Docker in Docker Using Sysbox Runtime&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Method+4%3A+Don%27t+run+docker+in+docker&quot;&gt;Method 4: Don&#039;t run docker in docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Conclusions&quot;&gt;Conclusions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/dind/docker-512.png&quot; alt=&quot;docker-logo&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;Introduction&quot; name=&quot;Introduction&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;In simple terms, Docker Inside Docker involves running Docker within a Docker container.&lt;/p&gt;
&lt;p&gt;These are the major use cases that I know of for doing this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Running an application to manage docker from a container (Example of this is
&lt;a href=&quot;https://www.portainer.io/&quot;&gt;portainer&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Building docker containers from within a container
&lt;ul&gt;
&lt;li&gt;CI/CD pipelines that build and push docker images to a container registry.&lt;/li&gt;
&lt;li&gt;Modern CI/CD systems that support Docker-based runners&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Container isolation for: multi-tenancy, enhanced security, resource management.
&lt;ul&gt;
&lt;li&gt;Sandboxed environments&lt;/li&gt;
&lt;li&gt;For experimental purposes on your local development workstation.&lt;/li&gt;
&lt;li&gt;Resource isolation between different groups or development teams.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This use cases bring the following benefits (&lt;a href=&quot;https://medium.com/@shivam77kushwah/docker-inside-docker-e0483c51cc2c&quot;&gt;Quote&lt;/a&gt;):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Isolated Development and Testing:&lt;br /&gt;Running Docker inside Docker allows developers to create isolated environments specifically
tailored for their applications. This ensures that dependencies, configurations, and runtime
environments remain consistent across different development stages, making it easier to
reproduce and debug issues.&lt;/li&gt;
&lt;li&gt;Enhanced Security and Isolation:&lt;br /&gt;Running Docker inside Docker allows developers to create isolated environments specifically
tailored for their applications. This ensures that dependencies, configurations, and runtime
environments remain consistent across different development stages, making it easier to
reproduce and debug issues.&lt;/li&gt;
&lt;li&gt;Simplified CI/CD Pipelines:&lt;br /&gt;Docker Inside Docker is widely used in Continuous Integration and Continuous Deployment (CI/CD)
workflows. It enables the creation of self-contained, disposable environments for building,
testing, and deploying applications, allowing for faster and more reliable automation pipelines.&lt;/li&gt;
&lt;li&gt;Multi-tenancy and Resource Management:&lt;br /&gt;Nested containerization can be incredibly useful in scenarios where multiple teams or users
require isolated environments on shared infrastructure. By launching Docker inside Docker,
you can provide each team or user with a separate Docker engine, ensuring resource isolation and
preventing interference between different applications.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There are a number of approaches for doing this depending on the use case one
may be more appropriate than others:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Mounting &lt;code&gt;/var/run/docker.sock&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Run Docker in Docker using DnD&lt;/li&gt;
&lt;li&gt;Run Docker in Docker using Sysbox run-time&lt;/li&gt;
&lt;li&gt;Don&#039;t run Docker in Docker&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;See &lt;a href=&quot;https://devopscube.com/run-docker-in-docker/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;Method+1%3A+Mounting+%2Fvar%2Frun%2Fdocker.sock&quot; name=&quot;Method+1%3A+Mounting+%2Fvar%2Frun%2Fdocker.sock&quot;&gt;Method 1: Mounting &lt;code&gt;/var/run/docker.sock&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/dind/docker-docker-unix-socket.png&quot; alt=&quot;docker-unix-socket&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/var/run/docker.sock&lt;/code&gt; is the default Unix socket. Sockets are meant for communication
between processes on the same host.&lt;/p&gt;
&lt;p&gt;Docker daemon by default listens to &lt;code&gt;docker.sock&lt;/code&gt;. If you are on the same host where the Docker
daemon is running, you can use the &lt;code&gt;/var/run/docker.sock&lt;/code&gt; to manage containers. meaning you can
mount the Docker socket from the host into the container&lt;/p&gt;
&lt;p&gt;For example, if you run the following command, it will return the version of the docker engine.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl --unix-socket /var/run/docker.sock http://localhost/version&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To run docker inside docker, all you have to do is run docker with the default Unix socket
&lt;code&gt;docker.sock&lt;/code&gt; as a volume.&lt;/p&gt;
&lt;p&gt;For example,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker run -v /var/run/docker.sock:/var/run/docker.sock \
           -ti docker&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Just a word of caution&lt;/strong&gt;: If your container gets access to &lt;code&gt;docker.sock&lt;/code&gt;, it means it has
more privileges over your docker daemon. So when used in real projects, understand the
security risks, and use it.&lt;/p&gt;
&lt;p&gt;Now, from within the container, you should be able to execute docker commands for building and
pushing images to the registry.&lt;/p&gt;
&lt;p&gt;Here, the actual docker operations happen on the VM host running your base docker container
rather than from within the container. Meaning, even though you are executing the docker commands
from within the container, you are instructing the docker client to connect to the VM host
docker-engine through docker.sock&lt;/p&gt;
&lt;p&gt;This method is the way to run applications like &lt;a href=&quot;https://www.portainer.io/&quot;&gt;portainer&lt;/a&gt; which are used
to manage docker environments.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/dind/portainer-infrastructure.png&quot; alt=&quot;portainer&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Because of the security implications I wouldn&#039;t recommend this for building docker images or
for doing container isolation.&lt;/p&gt;
&lt;h3 id=&quot;docker.sock+permission+error&quot; name=&quot;docker.sock+permission+error&quot;&gt;docker.sock permission error&lt;/h3&gt;
&lt;p&gt;While using &lt;code&gt;docker.sock&lt;/code&gt; you may get permission denied error. In that case, you need to change
the docker.sock permission to the following.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo chmod 666 /var/run/docker.sock&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also, you might have to add the &lt;code&gt;--privileged&lt;/code&gt; flag to give privileged access.&lt;/p&gt;
&lt;p&gt;The docker sock permission gets reset server restarts. To avoid this you need to add the
permission to system startup scripts.&lt;/p&gt;
&lt;p&gt;For example, you can add the command to &lt;code&gt;/etc/rc.local&lt;/code&gt; so that it runs automatically every time
your server starts up.&lt;/p&gt;
&lt;p&gt;Also, Keep in mind that &lt;code&gt;666&lt;/code&gt; permissions open a security hole.&lt;/p&gt;
&lt;h2 id=&quot;Method+2%3A+Docker+in+Docker+Using+DinD&quot; name=&quot;Method+2%3A+Docker+in+Docker+Using+DinD&quot;&gt;Method 2: Docker in Docker Using DinD&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/dind/docker-dind-min.png&quot; alt=&quot;docker-dind&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This method actually creates a child container inside a Docker container. Use this method only if you
really want to have the containers and images inside the container. Otherwise, I would suggest you
use the first approach.&lt;/p&gt;
&lt;p&gt;For this, you just need to use the official docker image with &lt;code&gt;dind&lt;/code&gt; tag. The dind image is baked
with the required utilities for Docker to run inside a docker container.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: This requires your container to be run in privileged mode.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Step 1: Create a container named &lt;code&gt;dind-test&lt;/code&gt; with &lt;code&gt;docker:dind&lt;/code&gt; image
&#039;&#039;&#039;bash
docker run --privileged -d --name dind-test docker:dind
&lt;pre&gt;&lt;code&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Step 2: Log in to the container using exec.
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker exec -it dind-test /bin/sh&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Step 3: Once you are inside the container, execute docker commands as needed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This approach is usually geared towards creating Docker images within docker.  The main
disadvantage is that you are still runnning your container in &lt;em&gt;priviledge mode&lt;/em&gt; which
can be considered a security risk.&lt;/p&gt;
&lt;h2 id=&quot;Method+3%3A+Docker+in+Docker+Using+Sysbox+Runtime&quot; name=&quot;Method+3%3A+Docker+in+Docker+Using+Sysbox+Runtime&quot;&gt;Method 3: Docker in Docker Using Sysbox Runtime&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/dind/sysbox-diagram.png&quot; alt=&quot;sysbox-diag&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This method avoids running containers in priviledge mode by using the &lt;a href=&quot;https://github.com/nestybox/sysbox&quot;&gt;sysbox&lt;/a&gt; runtime.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/nestybox/sysbox&quot;&gt;Sysbox&lt;/a&gt; is an open-source and free container runtime (a specialized &amp;quot;runc&amp;quot;), that
enhances containers in two key ways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Improves container isolation:
&lt;ul&gt;
&lt;li&gt;Linux user-namespace on all containers (i.e., root user in the container has zero
privileges on the host).&lt;/li&gt;
&lt;li&gt;Virtualizes portions of procfs &amp;amp; sysfs inside the container.&lt;/li&gt;
&lt;li&gt;Hides host info inside the container.&lt;/li&gt;
&lt;li&gt;Locks the container&#039;s initial mounts, and more.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Enables containers to run same workloads as VMs:
&lt;ul&gt;
&lt;li&gt;With &lt;a href=&quot;https://github.com/nestybox/sysbox&quot;&gt;Sysbox&lt;/a&gt;, containers can run system-level software such as systemd, Docker,
Kubernetes, K3s, buildx, legacy apps, and more seamlessly &amp;amp; securely.&lt;/li&gt;
&lt;li&gt;This software can run inside &lt;a href=&quot;https://github.com/nestybox/sysbox&quot;&gt;Sysbox&lt;/a&gt; containers without modification and without
using special versions of the software (e.g., rootless variants).&lt;/li&gt;
&lt;li&gt;No privileged containers, no complex images, no tricky entrypoints, no special volume mounts, etc.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you create a container using &lt;a href=&quot;https://github.com/nestybox/sysbox&quot;&gt;sysbox&lt;/a&gt; runtime, it can create virtual environments
inside a container that is capable of running systemd, docker, kubernetes without having
privileged access to the underlying host system.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Step 1: Install the sysbox runtime environment.&lt;/li&gt;
&lt;li&gt;Step 2: Once you have the sysbox runtime available, all you have to do is start the docker
container with a sysbox runtime flag as shown below. Here we are using the official docker dind
image.
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker run --runtime=sysbox-runc --name sysbox-dind -d docker:dind&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Step 3: Now take an exec session to the sysbox-dind container.
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker exec -it sysbox-dind /bin/sh&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Step 4: Once you are inside the container, execute docker commands as needed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This method is intented for sandboxing, running Container as VMs, isolating, etc.  Let&#039;s
you run &lt;em&gt;system&lt;/em&gt; software that requires special priviledges into isolated and secure
environment.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/nestybox/sysbox&quot;&gt;Sysbox&lt;/a&gt; solves problems such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Enhancing the isolation of containerized microservices (root in the container maps to an
unprivileged user on the host).&lt;/li&gt;
&lt;li&gt;Enabling a highly capable root user inside the container without compromising host security.&lt;/li&gt;
&lt;li&gt;Securing CI/CD pipelines by enabling Docker-in-Docker (DinD) or Kubernetes-in-Docker (KinD)
without insecure privileged containers or host Docker socket mounts.&lt;/li&gt;
&lt;li&gt;Enabling the use of containers as &amp;quot;VM-like&amp;quot; environments for development, local testing,
learning, etc., with strong isolation and the ability to run systemd, Docker, IDEs, and more
inside the container.&lt;/li&gt;
&lt;li&gt;Running legacy apps inside containers (instead of less efficient VMs).&lt;/li&gt;
&lt;li&gt;Replacing VMs with an easier, faster, more efficient, and more portable container-based
alternative, one that can be deployed across cloud environments easily.&lt;/li&gt;
&lt;li&gt;Partitioning bare-metal hosts into multiple isolated compute environments with 2X the density
of VMs (i.e., deploy twice as many VM-like containers as VMs on the same hardware at the same
performance).&lt;/li&gt;
&lt;li&gt;Partitioning cloud instances (e.g., EC2, GCP, etc.) into multiple isolated compute
environments without resorting to expensive nested virtualization.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Method+4%3A+Don%27t+run+docker+in+docker&quot; name=&quot;Method+4%3A+Don%27t+run+docker+in+docker&quot;&gt;Method 4: Don&#039;t run docker in docker&lt;/h2&gt;
&lt;p&gt;If you only need to docker images (For example inside a CI/CD pipeline with Docker or K8s
executors), you can simply use a tool that is focused on building docker images &lt;strong&gt;without&lt;/strong&gt;
using docker itself.  For example:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/dind/buildah-logo_sm.png&quot; alt=&quot;buildah&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/containers/buildah&quot;&gt;buildah&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/genuinetools/img&quot;&gt;img&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These two are available in &lt;a href=&quot;https://voidlinux.org/&quot;&gt;void linux&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;Conclusions&quot; name=&quot;Conclusions&quot;&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;Running docker in docker used to be a requirement to build docker images from within
a docker container (as part of a CI/CD pipeline based on docker container executors).&lt;/p&gt;
&lt;p&gt;Today in 2024, that is no longer needed, one you can just use a solution as indicated
in &lt;strong&gt;Method 4&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;However, docker in docker solutions are still needed to run things like
&lt;a href=&quot;https://www.portainer.io/&quot;&gt;portainer&lt;/a&gt; using &lt;strong&gt;Method 1&lt;/strong&gt; to run docker management solutions from
within a container.  I remember seeing other applications such as Infobox Blox One DDI
that took a similar approach.&lt;/p&gt;
&lt;p&gt;Lastly, there is still a need to run Containers as VMs or to create Docker like environments
for testing and isolation.  This use case exists, but I find it a bit niche.  In my view,
you are better off runninge these as a full VM which gives you better flexibility and more
discrete isolation.  It is less efficient, but in today&#039;s world, the overhead due to virtualization
is minimal compared to the complexity these special solutions bring.&lt;/p&gt;</content>
</entry>
<entry>
<title>Telekom Cloud CI/CD demo</title>
<link href="https://www.0ink.net/posts/2024/2024-09-15-telekom-cloud-demo.html"></link>
<id>urn:uuid:ea86dd9d-0d45-aff1-8110-f06f2c5eed31</id>
<updated>2024-11-22T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Pre-requisites
Preparation
Base infrastructure

Notes
...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Pre-requisites&quot;&gt;Pre-requisites&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Preparation&quot;&gt;Preparation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Base+infrastructure&quot;&gt;Base infrastructure&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Notes&quot;&gt;Notes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Sample+Workload&quot;&gt;Sample Workload&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Accessing+Nginx&quot;&gt;Accessing Nginx&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Notes&quot;&gt;Notes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Code+Repository&quot;&gt;Code Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Setting+up+Jenkins&quot;&gt;Setting up Jenkins&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Creating+pipeline&quot;&gt;Creating pipeline&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Build+steps&quot;&gt;Build steps&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Test+run+the+workload&quot;&gt;Test run the workload&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;a href=&quot;https://www.freepik.com/icon/software-development_8084296&quot;&gt;
&lt;img src=&quot;/images/2024/telekom-cloud/software-devs-256.png&quot; alt=&quot;swdev-icon by Freepik&quot; /&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;At work, I got asked to do a demo for a simple CI/CD pipeline on the &lt;a href=&quot;https://www.open-telekom-cloud.com/en&quot;&gt;Open Telekom Cloud&lt;/a&gt;.
Since it was short notice, I kept it simple and used the web console to set things up.
In the future, a Terraform script would have been more re-usable, but I did not have
enough time.&lt;/p&gt;
&lt;p&gt;The created CI/CD infrastructure is made of the following:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/telekom-cloud/gh-logo64.png&quot; alt=&quot;github&quot; /&gt;
&lt;img src=&quot;/images/2024/telekom-cloud/jenkinslogo64.png&quot; alt=&quot;jenkins&quot; /&gt;
&lt;img src=&quot;/images/2024/telekom-cloud/swr-icon64.png&quot; alt=&quot;SWR&quot; /&gt;
&lt;img src=&quot;/images/2024/telekom-cloud/cce-icon64.png&quot; alt=&quot;CCE&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Code Repository: Hosted on GitHub&lt;/li&gt;
&lt;li&gt;Jenkins CI : Hosted on Telekom Cloud VM
&lt;ul&gt;
&lt;li&gt;GitHub Integration (plugin)&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;SWR for Containers&lt;/li&gt;
&lt;li&gt;Cloud Container Engine
&lt;ul&gt;
&lt;li&gt;K8s Master&lt;/li&gt;
&lt;li&gt;Compute Nodes&lt;/li&gt;
&lt;li&gt;Shared Load Balancer&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Architecture:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/telekom-cloud/arch-view.png&quot; alt=&quot;arch-view&quot; /&gt;&lt;/p&gt;
&lt;p&gt;When implemented the CI/CD pipeline looks as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Trigger: code commit&lt;/li&gt;
&lt;li&gt;Prepare for build&lt;/li&gt;
&lt;li&gt;Build Java components via Maven&lt;/li&gt;
&lt;li&gt;Build Docker Image&lt;br /&gt;Push image to SWR&lt;/li&gt;
&lt;li&gt;Deploy Image to running Pod&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/telekom-cloud/pipeline.png&quot; alt=&quot;pipes&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Additional tasks can be added such as Static code analysis and linting, Unit tests,
Integration tests, Static application security testing, vulnerability scanning, etc.&lt;/p&gt;
&lt;h2 id=&quot;Pre-requisites&quot; name=&quot;Pre-requisites&quot;&gt;Pre-requisites&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;SSH key-pairs to login.
&lt;ul&gt;
&lt;li&gt;Go: &lt;strong&gt;Service List&lt;/strong&gt; &amp;rarr; &lt;strong&gt;Computing&lt;/strong&gt; &amp;rarr; &lt;strong&gt;Elastic Cloud Server&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Click: &lt;strong&gt;Cloud Server Console&lt;/strong&gt; ... &lt;em&gt;Network and Security&lt;/em&gt; ... &lt;strong&gt;Key Pair&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Preparation&quot; name=&quot;Preparation&quot;&gt;Preparation&lt;/h2&gt;
&lt;p&gt;To keep things clean, the first thing I like to do is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a tag for reporting.
&lt;ul&gt;
&lt;li&gt;Go: &lt;strong&gt;Service List&lt;/strong&gt; &amp;rarr; &lt;strong&gt;Management &amp;amp; Deployment&lt;/strong&gt; &amp;rarr; &lt;strong&gt;Tag Management Service&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Click: &lt;strong&gt;TMS&lt;/strong&gt; ... &lt;strong&gt;Predefined Tags&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Click: &lt;strong&gt;Create Tag&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: The pre-defined tag list doesn&#039;t seem to be honored in all possible scenarios.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Create a project for isolating resources.
&lt;ul&gt;
&lt;li&gt;Go: &lt;strong&gt;Service List&lt;/strong&gt; &amp;rarr; &lt;strong&gt;Management &amp;amp; Deployment&lt;/strong&gt; &amp;rarr; &lt;strong&gt;Identity and Access Management&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Click: &lt;strong&gt;IAM&lt;/strong&gt; ... &lt;strong&gt;Projects&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Click: &lt;strong&gt;Create Project&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Create an access group scopped to the project.
&lt;ul&gt;
&lt;li&gt;Go: &lt;strong&gt;Service List&lt;/strong&gt; &amp;rarr; &lt;strong&gt;Management &amp;amp; Deployment&lt;/strong&gt; &amp;rarr; &lt;strong&gt;Identity and Access Management&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Click: &lt;strong&gt;IAM&lt;/strong&gt; ... &lt;strong&gt;User Groups&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Click: &lt;strong&gt;Create User Group&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Select the &lt;em&gt;new group&lt;/em&gt; and click &lt;strong&gt;Authorize&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Select Policy/Roles:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tenant Administrator&lt;/strong&gt; : I suspect this is the only needed one.&lt;/li&gt;
&lt;li&gt;The following are used by the &lt;em&gt;Full admin&lt;/em&gt; role, while I have not tested them
are not needed:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Agent Operator&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Security Administrator&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Next&lt;/strong&gt; and select the &lt;strong&gt;Scope&lt;/strong&gt;.  Change to &lt;strong&gt;Region-specific projects&lt;/strong&gt; and
select the project as created in the previous step.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Ok&lt;/strong&gt; to confirm.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Create an account scoped for the given project.
&lt;ul&gt;
&lt;li&gt;Go: &lt;strong&gt;Service List&lt;/strong&gt; &amp;rarr; &lt;strong&gt;Management &amp;amp; Deployment&lt;/strong&gt; &amp;rarr; &lt;strong&gt;Identity and Access Management&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Click: &lt;strong&gt;IAM&lt;/strong&gt; ... &lt;strong&gt;Users&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Save the Login Link for later:&lt;br /&gt;&lt;img src=&quot;/images/2024/telekom-cloud/login-link.png&quot; alt=&quot;login-link&quot; /&gt;&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create User&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Fill-in:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Username&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Description&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Access Type&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; disabled checked&gt; Programmatic access&lt;/li&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; disabled checked&gt; Management console access&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Credential Type&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; disabled checked&gt; Password&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Set now&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; disabled &gt; &lt;em&gt;untick&lt;/em&gt; Require password reset at first login&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Next&lt;/strong&gt; add the user group created in the previous step.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;Base+infrastructure&quot; name=&quot;Base+infrastructure&quot;&gt;Base infrastructure&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Create VPC
&lt;ul&gt;
&lt;li&gt;Go: &lt;strong&gt;Service List&lt;/strong&gt; &amp;rarr; &lt;strong&gt;Network&lt;/strong&gt; &amp;rarr; &lt;strong&gt;Virtual Private Cloud&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Create VPC&lt;/strong&gt;.  Don&#039;t forget to add tags.&lt;/li&gt;
&lt;li&gt;Configure the first subnet and add a tag for it.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Network Console&lt;/strong&gt; &amp;rarr; &lt;strong&gt;Access Control&lt;/strong&gt; &amp;rarr; &lt;strong&gt;Security Groups&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create Network Security Group&lt;/strong&gt;, and use the template for &lt;strong&gt;General-purpose
web server&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Modify the new network security group.  For Jenkins we need to add ports 8080 and 50000.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Create Jenkins CI host
&lt;ul&gt;
&lt;li&gt;Go: &lt;strong&gt;Service List&lt;/strong&gt; &amp;rarr; &lt;strong&gt;Computing&lt;/strong&gt; &amp;rarr; &lt;strong&gt;Elastic Cloud Server&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create ECS&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Select the project from &lt;strong&gt;Region&lt;/strong&gt;. &lt;/li&gt;
&lt;li&gt;Select flavor.  I suggest a &lt;code&gt;General-purpose&lt;/code&gt;
flavor as these are the most cost-effective. &lt;/li&gt;
&lt;li&gt;For the image, I used: &lt;strong&gt;Public Image&lt;/strong&gt; : &lt;strong&gt;Ubuntu&lt;/strong&gt; : &lt;strong&gt;22.04_latest&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;System disk&lt;/em&gt;: Used the default &lt;code&gt;High I/O&lt;/code&gt; setting, but changed the size to
around &lt;code&gt;100GB&lt;/code&gt; for this example.&lt;/li&gt;
&lt;li&gt;Pick the network and network security group created in the previous step.&lt;/li&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; disabled checked&gt; EIP set to Auto assign.&lt;/li&gt;
&lt;li&gt;Select the Key Pair to use.  And click &lt;strong&gt;Advanced Options&lt;/strong&gt; to apply tags.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;For this demo we are using a single VM for Jenkins CI host.  We use EIP so that
the VM can received Web Hooks from &lt;a href=&quot;https://github.com/&quot;&gt;GitHub&lt;/a&gt;.  Since Jenkins is Java based
I would recommend at least 4GB of memory.&lt;/li&gt;
&lt;li&gt;Make sure there is sufficient storage.  Set the system disk to be at least
&lt;strong&gt;16 GB&lt;/strong&gt; or more.&lt;/li&gt;
&lt;li&gt;It is recommended to create a suitable DNS record.  Otherwise you would need
to use IP addresses.&lt;/li&gt;
&lt;li&gt;After the VM is created you can use the SSH keys to log in.  The default user
name is &lt;code&gt;ubuntu&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Install Java dependancies:
&lt;ul&gt;
&lt;li&gt;openjdk-17-jdk-headless openjdk-17-jre-headless maven&lt;/li&gt;
&lt;li&gt;In this example we are using Java v17 because some dependancies on the selected
applications.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/&quot;&gt;Install kubectl&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.jenkins.io/doc/book/installing/linux/&quot;&gt;Install and configure Jenkins&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Also install these additional packages:
&lt;ul&gt;
&lt;li&gt;docker.io plus cgroupfs-mount  debootstrap rinse pigz elfutils&lt;/li&gt;
&lt;li&gt;Add the &lt;code&gt;jenkins&lt;/code&gt; user to the &lt;code&gt;docker&lt;/code&gt; group:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;adduser user group&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The Jenkins service may need to be restarted.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Retrieve initial password from: &lt;code&gt;/var/lib/jenkins/secrets/initialAdminPassword&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Create cloud container engine cluster
&lt;ul&gt;
&lt;li&gt;Go: &lt;strong&gt;Service List&lt;/strong&gt; &amp;rarr; &lt;strong&gt;Computing&lt;/strong&gt; &amp;rarr; &lt;strong&gt;Cloud Container Engine&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create Cluster&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Type&lt;/strong&gt;: &lt;strong&gt;CCE Standard Cluster&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Enter &lt;strong&gt;Cluster Name&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cluster Scale&lt;/strong&gt;: &lt;strong&gt;50 nodes&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Master Nodes&lt;/strong&gt;: &lt;strong&gt;Single&lt;/strong&gt;&lt;br /&gt;Make sure these two settings are set to &lt;strong&gt;50&lt;/strong&gt; and &lt;strong&gt;Single&lt;/strong&gt; so that you
get a free of cost set of master nodes.&lt;/li&gt;
&lt;li&gt;Select the &lt;strong&gt;VPC&lt;/strong&gt; and &lt;strong&gt;Subnet&lt;/strong&gt; created in a previous step.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Network Model&lt;/strong&gt;: &lt;strong&gt;VPC Network&lt;/strong&gt;&lt;br /&gt;Read &lt;a href=&quot;https://docs.otc.t-systems.com/cloud-container-engine/umn/network/container_network_models/overview.html&quot;&gt;Network model comparison&lt;/a&gt; for explanation, but the TLDR
is:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;VPC Network&lt;/strong&gt;: Has low overhead but only scales up to small-medium networks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tunnel Network&lt;/strong&gt;: Has higher overhead (thus lower performance) but
can scale to large networks.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Advanced Settings&lt;/strong&gt; to configure relevant tags.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Add-Ons&lt;/strong&gt; configuration can be left on defaults.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Add a node to the cluster by clicking on the &lt;strong&gt;Create Node&lt;/strong&gt; link.
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Specifications&lt;/strong&gt;: Change to a suitable side for the cluster nodes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OS&lt;/strong&gt;: I am using &lt;strong&gt;Ubuntu 22.04&lt;/strong&gt; as that is what I am personally familiar with.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Login Mode&lt;/strong&gt;: Select a key from the available key pairs.  You can use the
private key to log in to the cluster node (use &lt;code&gt;ubuntu&lt;/code&gt; for the user name
due to using the &lt;strong&gt;Ubuntu 22.04&lt;/strong&gt; image).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Network Settings&lt;/strong&gt;, change &lt;strong&gt;EIP&lt;/strong&gt; to &lt;strong&gt;Auto create&lt;/strong&gt; but make sure that the
&lt;strong&gt;Line&lt;/strong&gt; is set to &lt;strong&gt;Dynamic BGP&lt;/strong&gt; and &lt;strong&gt;Bandwidth&lt;/strong&gt; to &lt;strong&gt;1&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Advanced Settings&lt;/strong&gt; to configure relevant tags.&lt;/li&gt;
&lt;li&gt;In the &lt;strong&gt;Advanced Settings&lt;/strong&gt; you can add additional commands  to run pre or
post K8s installation.&lt;/li&gt;
&lt;li&gt;Conveniently you can create more than one node here by adjusting the quantity
at the bottom of the page.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Create Software Repository for Containers
&lt;ul&gt;
&lt;li&gt;Go: &lt;strong&gt;Service List&lt;/strong&gt; &amp;rarr; &lt;strong&gt;Application&lt;/strong&gt; &amp;rarr; &lt;strong&gt;Software Repository for Container&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create Organization&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Create a &lt;a href=&quot;https://docs.otc.t-systems.com/software-repository-container/umn/image_management/obtaining_a_long-term_valid_login_command.html#swr-01-1000&quot;&gt;Long-Term valid login command&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;At this point we have all the infrastructure needed.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/telekom-cloud/deploy.png&quot; alt=&quot;IaaS&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;Notes&quot; name=&quot;Notes&quot;&gt;Notes&lt;/h3&gt;
&lt;p&gt;This was all done from the web console.  Ideally all this should
have been generated via &lt;a href=&quot;https://opentofu.org/&quot;&gt;Terraform&lt;/a&gt; scripts.&lt;/p&gt;
&lt;p&gt;We are creating a VM to run Jenkins.  This could be replaced with a Jenkins Pod on the
K8s cluster with external executors as K8s pods.  Initially I did not do it like that
because of my dependancies on docker for the application build process.  However
you can use tools like the ones listed &lt;a href=&quot;https://snyk.io/blog/building-docker-images-kubernetes/&quot;&gt;here&lt;/a&gt; or &lt;a href=&quot;https://www.linkedin.com/pulse/how-build-docker-image-kubernetes-pod-razorops-fj6wc/&quot;&gt;there&lt;/a&gt; and still perform
the builds from a running K8s pod.  To create dynamic executor from K8s pods you
need the &lt;a href=&quot;https://plugins.jenkins.io/kubernetes/&quot;&gt;Kubernetes plugin&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For simplicity I am using Compute nodes with EIPs.  Probably it should be more secure
to remove the EIPs and use a NAT gateway if the compute nodes need to download
packages from external repositories.&lt;/p&gt;
&lt;h2 id=&quot;Sample+Workload&quot; name=&quot;Sample+Workload&quot;&gt;Sample Workload&lt;/h2&gt;
&lt;p&gt;To test that the infrastructure is working, we can deploy a sample workload:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/telekom-cloud/workload.png&quot; alt=&quot;workload&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Go: &lt;strong&gt;Service List&lt;/strong&gt; &amp;rarr; &lt;strong&gt;Computing&lt;/strong&gt; &amp;rarr; &lt;strong&gt;Cloud Container Engine&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Click the name of the target cluster to access the cluster console.&lt;/li&gt;
&lt;li&gt;In the navigation pane, choose &lt;strong&gt;Workloads&lt;/strong&gt;. Then, click &lt;strong&gt;Create Workload&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Configure the following parameters and retain the default value for other parameters:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Basic Info&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Workload Type&lt;/strong&gt;: Select &lt;strong&gt;Deployment&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Workload Name&lt;/strong&gt;: Set it to &lt;strong&gt;nginx&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Namespace&lt;/strong&gt;: Select &lt;strong&gt;default&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pods&lt;/strong&gt;: Set the quantity of pods to &lt;strong&gt;1&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Container Settings&lt;/strong&gt;&lt;br /&gt;Enter &lt;strong&gt;nginx:latest&lt;/strong&gt; in the &lt;strong&gt;Image Name&lt;/strong&gt; text box.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Service Settings&lt;/strong&gt;&lt;br /&gt;Click the plus sign (+) to create a Service for accessing the workload from an
external network. This example shows how to create a LoadBalancer. Configure the
following parameters in the window that slides out from the right:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Service Name&lt;/strong&gt;: Enter &lt;strong&gt;nginx&lt;/strong&gt;. The name of the Service is exposed to external
networks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Service Type&lt;/strong&gt;: Select &lt;strong&gt;LoadBalancer&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Service Affinity&lt;/strong&gt;: Retain the default value.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Load Balancer&lt;/strong&gt;: If a load balancer is available, select an existing load balancer.
If not, select &lt;strong&gt;Auto create&lt;/strong&gt; to create one.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ports&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Protocol&lt;/strong&gt;: Select &lt;strong&gt;TCP&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Service Port&lt;/strong&gt;: Set this parameter to &lt;strong&gt;80&lt;/strong&gt;, which is mapped to the container port.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Container Port&lt;/strong&gt;: port on which the application listens. For containers created
using the nginx image, set this parameter to &lt;strong&gt;80&lt;/strong&gt;. For other applications, set this
parameter to the port of the application.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create Workload&lt;/strong&gt;.&lt;br /&gt;Wait until the workload is created.&lt;br /&gt;The created Deployment will be displayed on the Deployments tab.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Accessing+Nginx&quot; name=&quot;Accessing+Nginx&quot;&gt;Accessing Nginx&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Obtain the external access address of Nginx.&lt;br /&gt;Click the Nginx workload name to enter its details page. On the page displayed, click
the &lt;strong&gt;Access Mode&lt;/strong&gt; tab, view the IP address of Nginx. The public IP address is the
external access address.&lt;/li&gt;
&lt;li&gt;Enter the &lt;strong&gt;external access address&lt;/strong&gt; in the address box of a browser. The following
shows the welcome page if you successfully access the workload.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/telekom-cloud/en-us_image_0000001798307901.png&quot; alt=&quot;nginx&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;Notes&quot; name=&quot;Notes&quot;&gt;Notes&lt;/h3&gt;
&lt;p&gt;For this demo we are letting CCE create the load balancer.  And the load balancer rules
are then managed directly by it.  Probably creating the load balancer should be done
via &lt;a href=&quot;https://opentofu.org/&quot;&gt;Terraform&lt;/a&gt; but I have not tried this.  In theory you could use &lt;code&gt;kubectl&lt;/code&gt; to
create the service as described &lt;a href=&quot;https://docs.otc.t-systems.com/cloud-container-engine/umn/network/service/loadbalancer/creating_a_loadbalancer_service.html#using-kubectl-to-create-a-service-using-an-existing-load-balancer&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;Code+Repository&quot; name=&quot;Code+Repository&quot;&gt;Code Repository&lt;/h2&gt;
&lt;p&gt;For this demonstration we are using a &lt;a href=&quot;https://github.com/&quot;&gt;github&lt;/a&gt; hosted code repository.  Since
our customer made use of the &lt;a href=&quot;https://spring.io/&quot;&gt;Spring Framework&lt;/a&gt; I chose to use the
&lt;a href=&quot;https://github.com/spring-projects/spring-petclinic&quot;&gt;Spring Petclinic&lt;/a&gt; example application.  Note that this example
is interesting as there is a &lt;a href=&quot;https://github.com/spring-petclinic&quot;&gt;github org&lt;/a&gt; containing
different variations of the &lt;a href=&quot;https://github.com/spring-projects/spring-petclinic&quot;&gt;petclinic&lt;/a&gt;, such as different frontends,
microservices architecture, clouds, etc.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/telekom-cloud/petclinic.png&quot; alt=&quot;petclinic&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Create your code repository in &lt;a href=&quot;https://github.com/&quot;&gt;github&lt;/a&gt;.  You can do this by either doing a
&lt;em&gt;Fork&lt;/em&gt; of the &lt;a href=&quot;https://github.com/spring-projects/spring-petclinic&quot;&gt;Spring Petclinic&lt;/a&gt; application or using the &lt;em&gt;Import&lt;/em&gt;
functionality.  The difference between the two is that &lt;em&gt;Forking&lt;/em&gt; will keep the
link between the original repository and your new repository, whereas
&lt;em&gt;importing&lt;/em&gt; will keep the two repositories independant.  For the demo I chose
to &lt;em&gt;import&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;After creating the code repository link it to Jenkins CI:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Navigate to your code repository.&lt;/li&gt;
&lt;li&gt;Click on the &lt;strong&gt;Settings&lt;/strong&gt; tab.&lt;/li&gt;
&lt;li&gt;On the navigation pane, go to &lt;strong&gt;Webhooks&lt;/strong&gt; and click on &lt;strong&gt;Add webhook&lt;/strong&gt;.
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Payload URL&lt;/strong&gt; : &lt;em&gt;jenkins url&lt;/em&gt; &lt;code&gt;/github-webhook/&lt;/code&gt; e.g.&lt;br /&gt;&lt;code&gt;http://demo240619-ci.otc1.cloudkit7.xyz:8080/github-webhook/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Content type&lt;/strong&gt; : &lt;strong&gt;application/json&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This will trigger the webhook whenever changes are comitted.&lt;/p&gt;
&lt;h2 id=&quot;Setting+up+Jenkins&quot; name=&quot;Setting+up+Jenkins&quot;&gt;Setting up Jenkins&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/telekom-cloud/jenkinslogo64.png&quot; alt=&quot;jenkins&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Browse to the Jenkins CI web page.  It defaults to port 8080.  You will need the
password from:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/var/lib/jenkins/secrets/initialAdminPassword&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;I configure the following plugins:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; disabled checked&gt; Dashboard View - nice dashboard view.&lt;/li&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; disabled checked&gt; Credentials Binding - Needed to use credentials&lt;/li&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; disabled checked&gt; Git&lt;/li&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; disabled checked&gt; GitHub&lt;/li&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; disabled checked&gt; Locale - Enabled this because otherwise things show as being partially translated.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I did not test these, but seem useful:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; disabled checked&gt; Config File Provider - Use it to store &lt;code&gt;kubectl&lt;/code&gt; config?&lt;/li&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; disabled checked&gt; Embeddable Build Status - add badges to your repos?&lt;/li&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; disabled checked&gt; Warnings - Extract warnings or issues from different tools&lt;/li&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; disabled checked&gt; Git Parameter - choose branches, tags or revisions&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let the set-up wizard run.&lt;/p&gt;
&lt;p&gt;Create the first admin user.  Modify the Jenkins URL if necessary.  You may need to change
the Jenkins URL if running behind a reverse proxy.&lt;/p&gt;
&lt;h2 id=&quot;Creating+pipeline&quot; name=&quot;Creating+pipeline&quot;&gt;Creating pipeline&lt;/h2&gt;
&lt;p&gt;Go to the Jenkins CI main page and Click &lt;strong&gt;New Item&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Enter an item name&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Frestyle project&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/telekom-cloud/new_project.png&quot; alt=&quot;cicd pipeline&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Configure:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitHub project&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Enter &lt;strong&gt;Project url&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Source Code Management&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Choose &lt;strong&gt;Git&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Enter &lt;strong&gt;Repository URL&lt;/strong&gt;.  Add credentials if needed.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Build Triggers&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; disabled checked&gt; &lt;strong&gt;GitHub hook trigger for GITScm plling&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Build Environment&lt;/strong&gt; &amp;rarr; Enable &lt;strong&gt;Use secret text(s) or file(s)&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Add &lt;strong&gt;Secret File&lt;/strong&gt;: Use this to include the &lt;code&gt;kubeconfig&lt;/code&gt; file.&lt;/li&gt;
&lt;li&gt;Add &lt;strong&gt;Username and password (separated)&lt;/strong&gt;: Use this to include docker push credentials
and bind to &lt;code&gt;SWR_USER&lt;/code&gt; and &lt;code&gt;SWR_PASSWD&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Build+steps&quot; name=&quot;Build+steps&quot;&gt;Build steps&lt;/h3&gt;
&lt;p&gt;For the build steps I am just using shell scripts.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/telekom-cloud/miro512.png&quot; alt=&quot;build&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Common vars
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cat &amp;gt; vars.sh &amp;lt;&amp;lt;-_EOF_
swr=swr.eu-de.otc.t-systems.com
org=demo240619
app=spring_petclinic
ver=3.3.0
k8sid=&quot;spring-petclinic1&quot;
_EOF_&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Maven build
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;./mvnw clean package -Dmaven.test.skip=true&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Image build
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;(set +x ; docker login -u &quot;${SWR_USER}&quot;  -p &quot;${SWR_PASSWD}&quot; $swr )
docker buildx build -t $app:$ver-$BUILD_NUMBER .
docker tag &quot;$app:$ver-$BUILD_NUMBER&quot; &quot;$swr/$org/$app:$ver-$BUILD_NUMBER&quot;
docker push &quot;$swr/$org/$app:$ver-$BUILD_NUMBER&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Update running pods&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;k8scmd=&quot;kubectl --kubeconfig=$KUBECFG&quot;
$k8scmd config use-context internal 
if [ -z &quot;$($k8scmd get deployment | awk -vID=&quot;$k8sid&quot; &#039;$1 == ID&#039;)&quot; ] ; then
  echo &quot;Deployment not found&quot;
  exit 0
fi
$k8scmd set image deployment/$k8sid container-1=&quot;$swr/$org/$app:$ver-$BUILD_NUMBER&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that this step will be skipped since there won&#039;t be any running pods.  They
are created later.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Up to here, we have a Jenkins pipeline that&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;compiles a jar file&lt;/li&gt;
&lt;li&gt;creates a docker image&lt;/li&gt;
&lt;li&gt;pushes the image to the SWR for containers.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;Test+run+the+workload&quot; name=&quot;Test+run+the+workload&quot;&gt;Test run the workload&lt;/h2&gt;
&lt;p&gt;Follow the steps as outlined &lt;code&gt;Sample Workload&lt;/code&gt;.  Select the image created by
the Jenkins Pipeline.&lt;/p&gt;
&lt;p&gt;Updates to the source code will trigger a webhook that will start the
Jenkins pipeline.  The pipeline will update the running pod to the newly build
application.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/telekom-cloud/jenkins-wf512.png&quot; alt=&quot;Jenkins Workflow&quot; /&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>Linux desktop default apps</title>
<link href="https://www.0ink.net/posts/2024/2024-09-01-linux-apps.html"></link>
<id>urn:uuid:67e93a2d-6f6c-ba37-121e-4f6e0a357b8a</id>
<updated>2024-11-22T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
User configuration
System-wide configuration
Additional files
References

...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#User+configuration&quot;&gt;User configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#System-wide+configuration&quot;&gt;System-wide configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Additional+files&quot;&gt;Additional files&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#References&quot;&gt;References&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/freedesktop-logo.png&quot; alt=&quot;freedesktop&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This is one of the most annoying bits about configuring things in Linux.&lt;/p&gt;
&lt;p&gt;When you install more than one Linux application that handles the same file type
sometimes the default application that will be run will not match your expectations.
To check what application will be run you can use the UI or you can use check
it from the command line using the &lt;code&gt;xdg-mime&lt;/code&gt; command.  Examples:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;xdg-mime query default x-scheme-handler/mailto
xdg-mime query default image/jpeg&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;User+configuration&quot; name=&quot;User+configuration&quot;&gt;User configuration&lt;/h2&gt;
&lt;p&gt;You can use your Linux Desktop environment&#039;s User interface to set you
&lt;strong&gt;preferred&lt;/strong&gt; application to handle that file type.&lt;/p&gt;
&lt;p&gt;In the case of &lt;a href=&quot;https://lxqt-project.org/&quot;&gt;LXQT&lt;/a&gt; which is the current Desktop Environment that I am using
this can be found in:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Preferences &amp;rarr; LXQt Settings &amp;rarr; File Associations&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Or run: &lt;code&gt;lxqt-config-file-associations&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/file-associations.png&quot; alt=&quot;file-associations&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If you want to modify files this configuration can found here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;~/.config/mimeapps.list&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;~/.config/lxqt-mimeapps.list&lt;/code&gt; &lt;strong&gt;Only in LXQt&lt;/strong&gt;, in case you have application defaults
specific to &lt;a href=&quot;https://lxqt-project.org/&quot;&gt;LXQt&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These files are in standard &lt;code&gt;.ini&lt;/code&gt; format, example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;[Default Applications]
x-scheme-handler/http=Firefox.desktop
x-scheme-handler/https=Firefox.desktop
text/html=Firefox.desktop
application/pdf=qpdfview.desktop
inode/directory=pcmanfm-qt.desktop
image/jpeg=lximage-qt.desktop
image/jpg=lximage-qt.desktop
image/png=lximage-qt.desktop
image/gif=lximage-qt.desktop
image/webp=lximage-qt.desktop&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this file you can specify:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The default application to run when double clicking an icon&lt;/li&gt;
&lt;li&gt;The list of applications that will be shown in the &lt;code&gt;Open with...&lt;/code&gt; menu&lt;/li&gt;
&lt;li&gt;The list of applications that will be &lt;strong&gt;REMOVED&lt;/strong&gt; from the &lt;code&gt;Open with...&lt;/code&gt; menu&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For more info on the file format see &lt;a href=&quot;https://specifications.freedesktop.org/mime-apps-spec/mime-apps-spec-1.0.1.html#associations&quot;&gt;specs&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;Additonally you can use the &lt;code&gt;xdg-mime&lt;/code&gt; command to modify the defaults.  Examples:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;xdg-mime default thunderbird.desktop x-scheme-handler/mailto
xdg-mime default firefox-esr.desktop text/html
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;System-wide+configuration&quot; name=&quot;System-wide+configuration&quot;&gt;System-wide configuration&lt;/h2&gt;
&lt;p&gt;For configuring the default applications for all users you can use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/etc/xdg/mimeapps.list&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The format follows the same spec as the user file and is documented in this [specification][spec].&lt;/p&gt;
&lt;p&gt;The configuration file location can be overriden with the &lt;code&gt;XDG_CONFIG_DIRS&lt;/code&gt; environment
variable.&lt;/p&gt;
&lt;h2 id=&quot;Additional+files&quot; name=&quot;Additional+files&quot;&gt;Additional files&lt;/h2&gt;
&lt;p&gt;Keep in mind if you are using &lt;a href=&quot;https://www.flatpak.org/&quot;&gt;Flatpak&lt;/a&gt;&#039;s, these will also introduce its own
extensions that you may need to pay attention to.&lt;/p&gt;
&lt;p&gt;Due ot the way of how the Linux desktop have evolved the following locations will also
be used.  Make sure these do not exist as they may cause unexpected configurations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/etc/lxqt-mimeapps.list&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/etc/mimeapps.list&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/etc/xdg/lxqt-mimeapps.list&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/usr/share/lxqt-mimeapps.list&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/usr/share/mimeapps.list&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/usr/local/share/applications/lxqt-mimeapps.list&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/usr/local/share/applications/mimeapps.list&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;~/.local/share/applications/lxqt-mimeapps.list&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;~/.local/share/applications/mimeapps.list&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;~/.local/share/applications/defaults.list&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;References&quot; name=&quot;References&quot;&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://askubuntu.com/questions/1306025/how-do-i-set-the-default-email-client-in-lxqt&quot;&gt;https://askubuntu.com/questions/1306025/how-do-i-set-the-default-email-client-in-lxqt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wiki.archlinux.org/title/XDG_MIME_Applications&quot;&gt;https://wiki.archlinux.org/title/XDG_MIME_Applications&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://specifications.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-latest.html#s2_layout&quot;&gt;https://specifications.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-latest.html#s2_layout&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wiki.debian.org/DefaultPrograms#GUI_Applications&quot;&gt;https://wiki.debian.org/DefaultPrograms#GUI_Applications&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://specifications.freedesktop.org/mime-apps-spec/mime-apps-spec-1.0.1.html#associations&quot;&gt;https://specifications.freedesktop.org/mime-apps-spec/mime-apps-spec-1.0.1.html#associations&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Linux Serial Consoles</title>
<link href="https://www.0ink.net/posts/2024/2024-08-15-serial-tips.html"></link>
<id>urn:uuid:e5b6d731-e23f-0015-c2c1-f766e6cd539f</id>
<updated>2026-04-07T00:00:00+02:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Intro
Hardware

Null modem
Cisco style ports
...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Intro&quot;&gt;Intro&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Hardware&quot;&gt;Hardware&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Null+modem&quot;&gt;Null modem&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Cisco+style+ports&quot;&gt;Cisco style ports&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Raspberry+Pi+serial+ports&quot;&gt;Raspberry Pi serial ports&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#USB+cables&quot;&gt;USB cables&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Configuration&quot;&gt;Configuration&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Boot+configuration&quot;&gt;Boot configuration&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#grub+config&quot;&gt;grub config&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#syslinux&quot;&gt;syslinux&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Raspberry+pi+configuration&quot;&gt;Raspberry pi configuration&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Linux+configuration&quot;&gt;Linux configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Configuring+getty&quot;&gt;Configuring getty&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Booting+Xen&quot;&gt;Booting Xen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Change+serial+parameters&quot;&gt;Change serial parameters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Resizing+the+console&quot;&gt;Resizing the console&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Accessing+the+Serial+Console&quot;&gt;Accessing the Serial Console&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Windows&quot;&gt;Windows&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Linux&quot;&gt;Linux&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#tinyserial&quot;&gt;tinyserial&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Serial+ports+in+VMs&quot;&gt;Serial ports in VMs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Using+%5BGNU+screen%5D%5Bscreen%5D+as+Serial+Console+Server&quot;&gt;Using [GNU screen][screen] as Serial Console Server&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/serial-coms.png&quot; alt=&quot;Serial coms&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;Intro&quot; name=&quot;Intro&quot;&gt;Intro&lt;/h2&gt;
&lt;p&gt;In my previous article I went about adding a serial port to a NAS.  The reason why this
interesting to me is because it lets me remote manage my home servers at a fairly low level.&lt;/p&gt;
&lt;p&gt;This works because I only use Linux for my home servers and only am interested in managing
the Linux boot process which the serial port lets me do.&lt;/p&gt;
&lt;p&gt;There are more powerful (and at the same more costly) alternatives for servers, such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Management boards like &lt;a href=&quot;https://en.wikipedia.org/wiki/Dell_DRAC&quot;&gt;DRAC&lt;/a&gt;, &lt;a href=&quot;https://en.wikipedia.org/wiki/HP_Integrated_Lights-Out&quot;&gt;ILO&lt;/a&gt;, &lt;a href=&quot;https://en.wikipedia.org/wiki/IBM_Remote_Supervisor_Adapter&quot;&gt;RSA&lt;/a&gt;.  These are propietary
to specific manufacturers, but they give the most control of managed servers and
can be quite costly.&lt;/li&gt;
&lt;li&gt;IP based KVM.  This also gives you full manageability while being an open solution.
Systems like this can also quite expensive.  The cheapest I found is &lt;a href=&quot;https://pikvm.org/&quot;&gt;Pi-KVM&lt;/a&gt;,
however this solution is still more expensive compared to simply adding a serial port.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The big advantage of these more expensive solution is that these give you access to the
BIOS menu and are compatible with graphical Operating Systems such as Windows.&lt;/p&gt;
&lt;p&gt;Linux on the other hand can work fine with only a serial console.  For Bootloader I
use &lt;a href=&quot;https://en.wikipedia.org/wiki/GNU_GRUB&quot;&gt;grub&lt;/a&gt; or &lt;a href=&quot;https://en.wikipedia.org/wiki/SYSLINUX&quot;&gt;syslinux&lt;/a&gt; which happily support serial consoles.&lt;/p&gt;
&lt;p&gt;Note, that for this to work you either need a on-board serial port or a PCI/PCIe/ISA
serial port.  A USB to Serial converter will not work as they are not enabled early in the
boot process.&lt;/p&gt;
&lt;p&gt;In addition, for Virtual Machines, it is useful to configure them with serial console
as these can be used early in the boot process of the operating system.&lt;/p&gt;
&lt;p&gt;Specially for debugging early boot problems, simply capturing the output of the serial
port as opposed to carefully watching the screen (be that physical or virtual)
can be quite useful.&lt;/p&gt;
&lt;p&gt;There used to be a line of products called &lt;a href=&quot;https://en.wikipedia.org/wiki/PC_Weasel_2000&quot;&gt;PC Weasel&lt;/a&gt;.  This was a line of
graphics card that instead of output to a monitor they would output to a serial port.
In addition to the graphics emulation, it would also provide keyboard emulation.  It
would be a manufacturer independent remote console.  Unfortunately, they are no longer
around.&lt;/p&gt;
&lt;h2 id=&quot;Hardware&quot; name=&quot;Hardware&quot;&gt;Hardware&lt;/h2&gt;
&lt;p&gt;Serial ports can be found on many motherboards.  They could either be standard DB-9
or DB-25 (very rare nowadays) or &lt;a href=&quot;https://en.wikipedia.org/wiki/Rollover_cable&quot;&gt;Cisco style&lt;/a&gt; console ports.  For system
that are not equiped with serial ports, adding a PCIe serial port is quite inexpensive
(usually around 20EUR).  However, these systems would need to have space for a PCIe
card.  I have seen serial port cards in M.2 format.  I have not tested these.&lt;/p&gt;
&lt;p&gt;On PCs, usually ports use a male DB9 connector.  This is an old convention related
to DTE/DCE terminology.  Back in the day, PCs were called DTE&#039;s (Data Terminal Equipment)
and the standard for DTE is to use a Male DB9 (or DB25) connector for DTEs.  The
PC would connect to a modem.  Modems were catagorized as DCE (Data COmmunication Equipment)
and the standard for DCE is to use a Female DB9 (or DB25) connector.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/usb_serial.png&quot; alt=&quot;USB serial cable&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Since we are connecting two PCs, we would have 2 male DB9 connectors on each end.  Now,
do &lt;em&gt;NOT&lt;/em&gt; use a simple gender changer to connect the two.  Unlike modern equipment that
can automatically negotiate transmit and receive pins, serial ports require what is
called a &lt;a href=&quot;https://en.wikipedia.org/wiki/Null_modem&quot;&gt;null-modem&lt;/a&gt; cable/connector.  So a gender changer is &lt;em&gt;not&lt;/em&gt; enough.&lt;/p&gt;
&lt;h3 id=&quot;Null+modem&quot; name=&quot;Null+modem&quot;&gt;Null modem&lt;/h3&gt;
&lt;p&gt;Null modem is a communication method to directly connect two DTEs (computer, terminal,
printer, etc.) using an RS-232 serial cable.&lt;/p&gt;
&lt;p&gt;Wiring:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;text-align: right;&quot;&gt;Signal&lt;/th&gt;
&lt;th style=&quot;text-align: right;&quot;&gt;One Side (DB9)&lt;/th&gt;
&lt;th style=&quot;text-align: center;&quot;&gt;Signal Direction&lt;/th&gt;
&lt;th&gt;Other Side (DB9)&lt;/th&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;Carrier Detect (CD)&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&amp;larr;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;DTR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;Received Data (RxD)&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&amp;larr;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;TxD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;Transmitted Data (TxD)&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&amp;rarr;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;RxD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;Data Terminal Ready (DTR)&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&amp;rarr;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;CD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;Data Terminal Ready (DTR)&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&amp;rarr;&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;DSR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;Ground (GND)&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;-&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;GND&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;Data Set Ready (DSR)&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;6&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&amp;larr;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;DTR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;Request to Send (RTS)&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;7&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&amp;rarr;&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;CTS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;Clear to Send (CTS)&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;8&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&amp;larr;&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;RTS&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;Cisco+style+ports&quot; name=&quot;Cisco+style+ports&quot;&gt;Cisco style ports&lt;/h3&gt;
&lt;p&gt;Some motherboards and small form factor (SFF) systems come with a serial port in
the form of a cisco compatible console port.  These are nice in the sense that
they can use a &lt;a href=&quot;https://en.wikipedia.org/wiki/Rollover_cable&quot;&gt;rollover cable&lt;/a&gt; which act as a null-modemm cable, so
you can connect these ports back to back (with the right cable), in addition
to being smaller than even a DB9 connector.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/CiscoConsoleCable.jpg&quot; alt=&quot;Cisco cable&quot; /&gt;&lt;/p&gt;
&lt;p&gt;While sometimes the right adaptors are not readily availabke, you can buy DB9
to RJ45 adaptors that need to be wired (&lt;a href=&quot;https://www.startech.com/en-nl/cables/gc98ff&quot;&gt;Example&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/db9_rj45.png&quot; alt=&quot;GC98FF&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This is not difficult to do and once wired everything snaps into place.&lt;/p&gt;
&lt;p&gt;The basic wiring is as follows:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Color&lt;/th&gt;
&lt;th&gt;RJ45 Pin&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;RTS&lt;/td&gt;
&lt;td&gt;Blue&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DTR&lt;/td&gt;
&lt;td&gt;Orange&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TxD&lt;/td&gt;
&lt;td&gt;Black&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GND&lt;/td&gt;
&lt;td&gt;Red&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GND&lt;/td&gt;
&lt;td&gt;Green&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RxD&lt;/td&gt;
&lt;td&gt;Yellow&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DSR&lt;/td&gt;
&lt;td&gt;Brown&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CTS&lt;/td&gt;
&lt;td&gt;White&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;For a female adapter, the wiring becomes:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/db9-pinout-female.png&quot; alt=&quot;db9-female-pinout&quot; /&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;DB9 Pin&lt;/th&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Color&lt;/th&gt;
&lt;th&gt;RJ45 Pin&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;CD&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;TXD&lt;/td&gt;
&lt;td&gt;Black&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;RXD&lt;/td&gt;
&lt;td&gt;Yellow&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;DSR&lt;/td&gt;
&lt;td&gt;Brown&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;GND&lt;/td&gt;
&lt;td&gt;Red&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;GND&lt;/td&gt;
&lt;td&gt;Green&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;DTR&lt;/td&gt;
&lt;td&gt;Orange&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;CTS&lt;/td&gt;
&lt;td&gt;White&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;RTS&lt;/td&gt;
&lt;td&gt;Blue&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Alternative Pin-out for cross-connection.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;DB9 Pin&lt;/th&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Color&lt;/th&gt;
&lt;th&gt;RJ45 Pin&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;CD&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;RXD&lt;/td&gt;
&lt;td&gt;Yellow&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;TXD&lt;/td&gt;
&lt;td&gt;Black&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;DTR&lt;/td&gt;
&lt;td&gt;Orange&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;GND&lt;/td&gt;
&lt;td&gt;Red&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;GND&lt;/td&gt;
&lt;td&gt;Green&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;DSR&lt;/td&gt;
&lt;td&gt;Brown&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;RTS&lt;/td&gt;
&lt;td&gt;Blue&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;CTS&lt;/td&gt;
&lt;td&gt;White&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;For a male adapter, the wiring meaning is reversed however the RJ45 to DB9 pin assignments are the same:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/db9-pinout-male.png&quot; alt=&quot;db9-male-pinout&quot; /&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;DB9 Pin&lt;/th&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Color&lt;/th&gt;
&lt;th&gt;RJ45 Pin&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;CD&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;RXD&lt;/td&gt;
&lt;td&gt;Black&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;TXD&lt;/td&gt;
&lt;td&gt;Yellow&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;DTR&lt;/td&gt;
&lt;td&gt;Brown&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;GND&lt;/td&gt;
&lt;td&gt;Red&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;GND&lt;/td&gt;
&lt;td&gt;Green&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;DSR&lt;/td&gt;
&lt;td&gt;Orange&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;RTS&lt;/td&gt;
&lt;td&gt;White&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;CTS&lt;/td&gt;
&lt;td&gt;Blue&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;Raspberry+Pi+serial+ports&quot; name=&quot;Raspberry+Pi+serial+ports&quot;&gt;Raspberry Pi serial ports&lt;/h3&gt;
&lt;p&gt;On a Rasbperry Pi, there is a serial port available in the GPIO header.  The configuration
of GPIO header is as follows:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/GPIO-Pinout-Diagram-1.png&quot; alt=&quot;gpio ports&quot; /&gt;&lt;/p&gt;
&lt;p&gt;(See &lt;a href=&quot;https://www.raspberrypi.com/documentation/computers/raspberry-pi.html&quot;&gt;Raspberry-Pi GPIO&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;For the built-in serial port is available on GPIO14 (TXD, pin8) and GPIO15 (RXD, pin10).
In addition you would need to connect a ground signal.  I would usually use the GND from
Pin6 as is adjacent to the TXD,RXD lines.  This is usually enough, however, if you are using
a TTL to Serial level convert, you would need to connect a 5V power supply.  In that case
I would use Pin4 (again, because it is adjacent).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;, the serial port in the Raspberry Pi is using TTL levels (3.3V).  Do &lt;em&gt;NOT&lt;/em&gt; connect
directly to a standard serial port interface.&lt;/p&gt;
&lt;h3 id=&quot;USB+cables&quot; name=&quot;USB+cables&quot;&gt;USB cables&lt;/h3&gt;
&lt;p&gt;While earlier I stated that using USB to Serial cables are not good for remote management,
that is not the case for the systems being used as console.  The reason is that in this
scenario, we are using the serial port after the system is up and running and &lt;em&gt;not&lt;/em&gt;
during the early boot process.  For that reason, for the console systems, USB cables
are OK.&lt;/p&gt;
&lt;p&gt;For me, I am using a Raspberry Pi 3B as a console server and/or a Windows laptop as
as direct system console.  For either use case, using the USB serial cable is OK.&lt;/p&gt;
&lt;p&gt;In the case of Linux, after you plug in the serial cable, they will be shown to
the user as &lt;code&gt;/dev/ttyUSB&lt;/code&gt;X.  Where &lt;code&gt;X&lt;/code&gt; is a number starting from &lt;code&gt;0&lt;/code&gt; (zero).&lt;/p&gt;
&lt;p&gt;In the case of Windows, the USB serial port are usually show as device &lt;code&gt;COM&lt;/code&gt;N.
Where &lt;code&gt;N&lt;/code&gt; is a number starting from &lt;code&gt;1&lt;/code&gt; (one).  However very often, USB port start
being numbered from &lt;code&gt;4&lt;/code&gt; onwards.&lt;/p&gt;
&lt;p&gt;For my console server use-case, I got a USB to 4 serial ports cable.  I preferred to
use this to using a USB-hub and having multiple USB to serial conversion because it
makes it simpler when enumerating the serial ports.&lt;/p&gt;
&lt;h2 id=&quot;Configuration&quot; name=&quot;Configuration&quot;&gt;Configuration&lt;/h2&gt;
&lt;h3 id=&quot;Boot+configuration&quot; name=&quot;Boot+configuration&quot;&gt;Boot configuration&lt;/h3&gt;
&lt;p&gt;Depending on the bootloader and your hardware you need to do certain configuration
changes for the serial port to be available during boot.&lt;/p&gt;
&lt;h4 id=&quot;grub+config&quot; name=&quot;grub+config&quot;&gt;grub config&lt;/h4&gt;
&lt;p&gt;If booting using &lt;a href=&quot;https://en.wikipedia.org/wiki/GNU_GRUB&quot;&gt;grub&lt;/a&gt; you need the following configuration settings
in your &lt;code&gt;grub.cfg&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;serial --unit=0 --speed=115200
terimanl_input console serial
terminal output console serial&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first line configures the serial port to use.  &lt;code&gt;0&lt;/code&gt; (zero) is the first serial port.  &lt;code&gt;1&lt;/code&gt; for
the second serial port.  Change this accordingly.&lt;/p&gt;
&lt;p&gt;Similarly, &lt;code&gt;--speed&lt;/code&gt; specifies the baud rate to use.  In the example we are using &lt;code&gt;115200&lt;/code&gt;.  Select
the appropriate speed.  Common values are: &lt;code&gt;9600&lt;/code&gt;, &lt;code&gt;38400&lt;/code&gt;, etc.&lt;/p&gt;
&lt;p&gt;The next two lines configure that input and output should go simultaneously to the display
console and the serial port.&lt;/p&gt;
&lt;h4 id=&quot;syslinux&quot; name=&quot;syslinux&quot;&gt;syslinux&lt;/h4&gt;
&lt;p&gt;To enable the serial port add the following line:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;SERIAL 0 115200&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As in the previous example, we are using the first serial port (0) and setting the speed to 115200.&lt;/p&gt;
&lt;h4 id=&quot;Raspberry+pi+configuration&quot; name=&quot;Raspberry+pi+configuration&quot;&gt;Raspberry pi configuration&lt;/h4&gt;
&lt;p&gt;For Raspberry Pi before the Pi3 the serial port is enabled by default.  From the Raspberry pi an
onwards, you need to add the config line to /&lt;code&gt;boot/config.txt&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;enable_Uart=1&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In my case I am also using an UEFI boot loader for loading grub as the boot menu.  If that
is the case, the UEFI bootloader will recognize the serial port automatically.  However
the Linux kernel needs to be passed the following command line to enable the serial port:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;8250.nr_uarts=1&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is done automatically by the Raspberry PI firmware, but if you are using &lt;a href=&quot;https://en.wikipedia.org/wiki/GNU_GRUB&quot;&gt;grub&lt;/a&gt;
to boot Linux, make sure you add this to the kernel command line.&lt;/p&gt;
&lt;h3 id=&quot;Linux+configuration&quot; name=&quot;Linux+configuration&quot;&gt;Linux configuration&lt;/h3&gt;
&lt;p&gt;The serial console can be configured using the kernel command line.  See
&lt;a href=&quot;https://www.kernel.org/doc/html/v4.16/admin-guide/serial-console.html&quot;&gt;serial-console&lt;/a&gt; article.&lt;/p&gt;
&lt;p&gt;By default under Linux, the current display and keyboard combination is the console.
You can redefine the console by adding the &lt;code&gt;console&lt;/code&gt; command line option in the
Linux kernel command line.  (See &lt;a href=&quot;https://www.kernel.org/doc/html/v4.14/admin-guide/kernel-parameters.html&quot;&gt;kernel options&lt;/a&gt; for all the options
available for the Linux kernel command line).&lt;/p&gt;
&lt;p&gt;For the console, the following options are possible:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;console=&lt;/code&gt; : Output console device and options.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;console&lt;/code&gt; can be specified multiple times.  The following options after the &lt;code&gt;=&lt;/code&gt;
equal sign are recognized:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;tty&amp;lt;n&amp;gt;&lt;/code&gt;  Use the virtual console device &lt;code&gt;&amp;lt;n&amp;gt;&lt;/code&gt;.&lt;br /&gt;If &lt;code&gt;&amp;lt;n&amp;gt;&lt;/code&gt; is zero &lt;code&gt;0&lt;/code&gt;, it will use the current foreground virtual console.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ttyS&amp;lt;n&amp;gt;[,options]&lt;/code&gt;&lt;br /&gt;&lt;code&gt;ttyUSB0[,options]&lt;/code&gt;&lt;br /&gt;Use the specified serial port.  The options are of
the form &amp;quot;bbbbpnf&amp;quot;, where &amp;quot;bbbb&amp;quot; is the baud rate,
&amp;quot;p&amp;quot; is parity (&amp;quot;n&amp;quot;, &amp;quot;o&amp;quot;, or &amp;quot;e&amp;quot;), &amp;quot;n&amp;quot; is number of
bits, and &amp;quot;f&amp;quot; is flow control (&amp;quot;r&amp;quot; for RTS or
omit it).  Default is &amp;quot;9600n8&amp;quot;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uart[8250],io,&amp;lt;addr&amp;gt;[,options]&lt;/code&gt;&lt;br /&gt;&lt;code&gt;uart[8250],mmio,&amp;lt;addr&amp;gt;[,options]&lt;/code&gt;&lt;br /&gt;&lt;code&gt;uart[8250],mmio16,&amp;lt;addr&amp;gt;[,options]&lt;/code&gt;&lt;br /&gt;&lt;code&gt;uart[8250],mmio32,&amp;lt;addr&amp;gt;[,options]&lt;/code&gt;&lt;br /&gt;&lt;code&gt;uart[8250],0x&amp;lt;addr&amp;gt;[,options]&lt;/code&gt;&lt;br /&gt;Start an early, polled-mode console on the 8250/16550
UART at the specified I/O port or MMIO address,
switching to the matching ttyS device later.
MMIO inter-register address stride is either 8-bit
(mmio), 16-bit (mmio16), or 32-bit (mmio32).
If none of [io|mmio|mmio16|mmio32], &lt;code&gt;&amp;lt;addr&amp;gt;&lt;/code&gt; is assumed
to be equivalent to &#039;mmio&#039;. &#039;options&#039; are specified in
the same format described for ttyS above; if unspecified,
the h/w is not re-initialized.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hvc&amp;lt;n&amp;gt;&lt;/code&gt; &lt;br /&gt;Use the hypervisor console device &lt;code&gt;&amp;lt;n&amp;gt;&lt;/code&gt;. This is for
both Xen and PowerPC hypervisors.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If &lt;code&gt;console&lt;/code&gt; is specified multiple times output will appear on all of them. The last device
will be used when you open &lt;code&gt;/dev/console&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For example, normally I would use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;console=tty0 console=ttyS0,115200&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This would generate output on the current foreground virtual console and the first serial port.
The &lt;code&gt;/dev/consoel&lt;/code&gt; device would be attached to the serial port.&lt;/p&gt;
&lt;p&gt;If no console device is specified, the first device found capable of acting as a system console
will be used. At this time, the system first looks for a VGA card and then for a serial port. So
if you don’t have a VGA card in your system the first serial port will automatically become the
console.&lt;/p&gt;
&lt;p&gt;You will need to create a new device to use &lt;code&gt;/dev/console&lt;/code&gt;. The official &lt;code&gt;/dev/console&lt;/code&gt; is
character device 5,1.&lt;/p&gt;
&lt;h3 id=&quot;Configuring+getty&quot; name=&quot;Configuring+getty&quot;&gt;Configuring getty&lt;/h3&gt;
&lt;p&gt;To enable logins on the serial port, make sure a &lt;code&gt;getty&lt;/code&gt; runs it, so you can login once the
system is done booting. This is done by adding a line like this to &lt;code&gt;/etc/inittab&lt;/code&gt; (exact syntax
depends on your &lt;code&gt;getty&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ttyS0::respawn:/sbin/getty -L 0 ttyS0 vt100&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Booting+Xen&quot; name=&quot;Booting+Xen&quot;&gt;Booting Xen&lt;/h3&gt;
&lt;p&gt;When booting Xen, you need to pass the following parameters to the &lt;code&gt;xen&lt;/code&gt; command line:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;com1=115200,8n1 console=com1&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This configures the first serial port and makes it the Xen console.  This effectively
hides that serial port from Linux.  Similarly the Linux kernel command line needs to have:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;console=tty0 console=hvc0&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, to enable logins add to &lt;code&gt;/etc/inittab&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;hvc0::respawn:/sbin/getty -L 0 hvc0 vt100&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When using xen on the serial port you can press &lt;code&gt;CTRL-a&lt;/code&gt; three times to switch between
&lt;code&gt;dom0&lt;/code&gt; and &lt;code&gt;xen&lt;/code&gt; input.  In &lt;code&gt;xen&lt;/code&gt; mode you have access to the low-level xen hypervisor.
Press &lt;code&gt;h&lt;/code&gt; for &lt;strong&gt;help&lt;/strong&gt; on available options.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;CTRL-a&lt;/code&gt; escape key can be configured from the &lt;code&gt;xen&lt;/code&gt; command line
using option:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;conswitch=&amp;lt;switch-char[x]&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The default is:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;conswitch=a&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The optional &lt;code&gt;x&lt;/code&gt; at the end means that &lt;code&gt;xen&lt;/code&gt; will not switch to &lt;code&gt;dom0&lt;/code&gt; but remain
in &lt;code&gt;xen&lt;/code&gt; input mode.&lt;/p&gt;
&lt;p&gt;During run-time, you can change the escape key using:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;xl set-parameters conswitch=k&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where &lt;code&gt;k&lt;/code&gt; is the character you want to use as escape.&lt;/p&gt;
&lt;h3 id=&quot;Change+serial+parameters&quot; name=&quot;Change+serial+parameters&quot;&gt;Change serial parameters&lt;/h3&gt;
&lt;p&gt;During run-time you can modify the serial port setting use the &lt;code&gt;stty&lt;/code&gt; command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;stty -a&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Will display all available settings.  You also check/set settings of a different
serial port:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;stty -a &amp;lt; /dev/ttyS1&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Resizing+the+console&quot; name=&quot;Resizing+the+console&quot;&gt;Resizing the console&lt;/h3&gt;
&lt;p&gt;When running interactive commands you may need to tell server the current size of your
screen.  This can be done with the command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;stty rows 25 cols 80&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We set the screen size to 80 columns, and 25 lines in this example.  To automate this
you can use this &lt;a href=&quot;https://github.com/alejandroliu/0ink.net/tree/main/snippets/2024/serial/rsz&quot;&gt;rsz&lt;/a&gt; script.&lt;/p&gt;
&lt;h2 id=&quot;Accessing+the+Serial+Console&quot; name=&quot;Accessing+the+Serial+Console&quot;&gt;Accessing the Serial Console&lt;/h2&gt;
&lt;p&gt;To access the serial console you need a computer with a serial port and connecting it
to the serial port of the managed system.  As mentioned above, you would normally
require a &lt;code&gt;null modem&lt;/code&gt; to perform the connection.&lt;/p&gt;
&lt;h3 id=&quot;Windows&quot; name=&quot;Windows&quot;&gt;Windows&lt;/h3&gt;
&lt;p&gt;In Windows you can use &lt;a href=&quot;https://www.putty.org/&quot;&gt;putty&lt;/a&gt;.  Either plug in a USB to serial converter or use
an on-board serial port.&lt;/p&gt;
&lt;p&gt;First determine the name of the port that the system allocated to the serial port using
&lt;strong&gt;device manager&lt;/strong&gt;.  The quickets way is to click on the &lt;strong&gt;Start&lt;/strong&gt; button and type in
&lt;code&gt;device manager&lt;/code&gt;.  It may warn you that you are running as standard user.  That is fine
as we only need to look up stuff, and &lt;strong&gt;not&lt;/strong&gt; make any changes.&lt;/p&gt;
&lt;p&gt;Expand &lt;strong&gt;Ports (COM &amp;amp; LPT)&lt;/strong&gt;, the serial adapter should be listed:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/sputty0.png&quot; alt=&quot;device manager&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Alternatively you can use the &lt;code&gt;mode&lt;/code&gt; command from the Windows command line.&lt;/p&gt;
&lt;p&gt;Open &lt;code&gt;PuTTY&lt;/code&gt;, and click &lt;strong&gt;Serial&lt;/strong&gt; from the Category: &lt;strong&gt;Connection&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/sputty1.png&quot; alt=&quot;connection settings&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Edit the settings, eg: COM1, 115200, 8, 1, None. Flow Control: None.&lt;/p&gt;
&lt;p&gt;Select the Category: &lt;strong&gt;Session&lt;/strong&gt;, click the &lt;strong&gt;Serial&lt;/strong&gt; radio button.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/sputty2.png&quot; alt=&quot;session settings&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If you want, you may save the session.&lt;/p&gt;
&lt;p&gt;Click &lt;strong&gt;Open&lt;/strong&gt; to start the serail session.&lt;/p&gt;
&lt;h3 id=&quot;Linux&quot; name=&quot;Linux&quot;&gt;Linux&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://www.gnu.org/software/screen/&quot;&gt;screen&lt;/a&gt; command can be used in Linux.  Install &lt;a href=&quot;https://www.gnu.org/software/screen/&quot;&gt;screen&lt;/a&gt; if not already installed.
Most distributions should have a package for it.&lt;/p&gt;
&lt;p&gt;To find the serial port device you can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ls /dev/ttyS*&lt;/code&gt; for on-board serial ports.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ls /dev/ttyUSB*&lt;/code&gt; for USB serial ports.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can now use the &lt;a href=&quot;https://www.gnu.org/software/screen/&quot;&gt;screen&lt;/a&gt; command to to establish a simple serial connection.&lt;/p&gt;
&lt;p&gt;Type &lt;code&gt;screen &amp;lt;port_name&amp;gt; &amp;lt;baud_rate&amp;gt;&lt;/code&gt; to create a connection:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;screen /dev/ttyUSB0 115200&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The terminal will go blank with just a cursor. You are now connected to that port!&lt;/p&gt;
&lt;p&gt;To disconnect, type &lt;code&gt;control-a&lt;/code&gt; followed by &lt;code&gt;shift-k&lt;/code&gt;. The screen will then ask if you are
sure you want to disconnect.&lt;/p&gt;
&lt;p&gt;There are other options you can control from &lt;code&gt;screen&lt;/code&gt;, however it is recommended that you
only use this method if you are comfortable with the command line. Type &lt;code&gt;man screen&lt;/code&gt; for a full
list of options and commands.&lt;/p&gt;
&lt;p&gt;To disconnect, type &lt;code&gt;control-a&lt;/code&gt; then &lt;code&gt;shift-k&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That&#039;s all there is to it.&lt;/p&gt;
&lt;h4 id=&quot;tinyserial&quot; name=&quot;tinyserial&quot;&gt;tinyserial&lt;/h4&gt;
&lt;p&gt;I myself use an uncomplicated command called &lt;a href=&quot;http://brokestream.com/tinyserial.html&quot;&gt;tinyserial&lt;/a&gt;.  This is a barebones
command that sets up the serial port and connects you to it.&lt;/p&gt;
&lt;p&gt;My local copy can be found &lt;a href=&quot;https://github.com/alejandroliu/0ink.net/tree/main/snippets/2024/serial/tinyserial&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;Serial+ports+in+VMs&quot; name=&quot;Serial+ports+in+VMs&quot;&gt;Serial ports in VMs&lt;/h3&gt;
&lt;p&gt;Configuring serial ports when creating Linux VMs lets you monitor the VM boot
process from the start.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Configuring
&lt;ul&gt;
&lt;li&gt;xen: enabled by default&lt;/li&gt;
&lt;li&gt;libvirt: Either add the following xml:
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;  &amp;lt;serial type=&quot;pty&quot;&amp;gt;
    &amp;lt;target port=&quot;0&quot;/&amp;gt;
  &amp;lt;/serial&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or via &lt;strong&gt;virt-manager&lt;/strong&gt; UI.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Connecting:
&lt;ul&gt;
&lt;li&gt;xen: &lt;code&gt;xl console vmname&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;libvirt: &lt;code&gt;virsh console vmname&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Using+%5BGNU+screen%5D%5Bscreen%5D+as+Serial+Console+Server&quot; name=&quot;Using+%5BGNU+screen%5D%5Bscreen%5D+as+Serial+Console+Server&quot;&gt;Using &lt;a href=&quot;https://www.gnu.org/software/screen/&quot;&gt;GNU screen&lt;/a&gt; as Serial Console Server&lt;/h3&gt;
&lt;p&gt;You can use &lt;a href=&quot;https://www.gnu.org/software/screen/&quot;&gt;GNU screen&lt;/a&gt; mentioned earlier, to set-up a Serial Console server.
For this Serial Console Server we have this functionality:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Let you connect to a serial port via the network&lt;/li&gt;
&lt;li&gt;Log traffic over the serial port to a file&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For this I am using a [Raspberry Pi]]rpi] with a multi-serial port USB adaptor.  During
system start-up I start &lt;a href=&quot;https://www.gnu.org/software/screen/&quot;&gt;GNU screen&lt;/a&gt; as a detached session and connect it to
the serial port.  Each serial port has a &lt;em&gt;named&lt;/em&gt; session.&lt;/p&gt;
&lt;p&gt;Using &lt;code&gt;ssh&lt;/code&gt; I log-on to the system and use &lt;code&gt;screen -r *SESSION*&lt;/code&gt; to re-attach to the
session.  The &lt;a href=&quot;https://www.gnu.org/software/screen/&quot;&gt;GNU screen&lt;/a&gt; command also logs all serial port traffic to
a file.&lt;/p&gt;
&lt;p&gt;To do this, I use the following options when starting &lt;a href=&quot;https://www.gnu.org/software/screen/&quot;&gt;screen&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-d -m&lt;/code&gt; : start the session in a detached/background state&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-L&lt;/code&gt; : turn on automatic output logging&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-Logfile file&lt;/code&gt; : and log to the given file.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-S name&lt;/code&gt; : give the session a name (useful for re-attaching)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I usually start the session with this additional arguments:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;/dev/ttyUSB0 115200,crtscts&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This set the given serial port to 115200 speed, and turns on RTS/CTS flow-control.&lt;/p&gt;
&lt;p&gt;I also use the &lt;code&gt;SCREENRC&lt;/code&gt; environment variable to specify an initialization file
with further configuration.&lt;/p&gt;</content>
</entry>
<entry>
<title>Adding a serial port to a QNAP TS-251D</title>
<link href="https://www.0ink.net/posts/2024/2024-08-01-qnap-ts251d-serial.html"></link>
<id>urn:uuid:b533e2b3-094f-0bb5-0bee-3315d3eae994</id>
<updated>2024-11-22T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
I am using a QNAP TS-251D NAS.  Because I would like to switch from QTS to
Alpine Linux I though it would be useful to enable the serial port.
The TS-251D has a built-in serial port that is already enabled and only needs to be
connected.  For that you need a number of parts:

...]]></summary>
<content type="html">&lt;p&gt;&lt;img src=&quot;/images/2024/3dp/img6.png&quot; alt=&quot;Connector&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I am using a &lt;a href=&quot;https://www.qnap.com/en/product/ts-251d&quot;&gt;QNAP TS-251D&lt;/a&gt; NAS.  Because I would like to switch from &lt;a href=&quot;https://www.qnap.com/qts/5.0/en/&quot;&gt;QTS&lt;/a&gt; to
&lt;a href=&quot;https://alpinelinux.org/&quot;&gt;Alpine Linux&lt;/a&gt; I though it would be useful to enable the serial port.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://www.qnap.com/en/product/ts-251d&quot;&gt;TS-251D&lt;/a&gt; has a built-in serial port that is already enabled and only needs to be
connected.  For that you need a number of parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.amazon.nl/dp/B0CTKSDDFT&quot;&gt;JST 4-pin connector&lt;/a&gt;.  I selected
&lt;a href=&quot;https://www.amazon.nl/dp/B0CTKSDDFT&quot;&gt;Adafruit accessoires JST PH 2mm 4-pins - 3950&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.amazon.nl/dp/B09L1BB6F8&quot;&gt;TTL to Serial port level converter&lt;/a&gt;.  I am using
&lt;a href=&quot;https://www.amazon.nl/dp/B09L1BB6F8&quot;&gt;JZK 4 PCs 3V-5V RS232 to TTL serial port module&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In addition, I am using a 3D printed bracket to hold the serial port.&lt;/p&gt;
&lt;p&gt;Examinig the QTS environment you can see that there is indeed a &lt;code&gt;/dev/ttyS0&lt;/code&gt; device (a serial
port) and it is enabled in the Kernel command line with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;console=ttyS0,115200n8&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Simmilarly you can find in the &lt;code&gt;/etc/inittab&lt;/code&gt; a line:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ttyS0::respawn:/sbin/getty -L ttyS0 115200 vt100&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So there is a serial port available and being used for debugging.&lt;/p&gt;
&lt;p&gt;To make it visible to the outside, you only need to connect to JST1&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/3dp/img3.png&quot; alt=&quot;Connector&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Pins from left to right.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;GND&lt;/li&gt;
&lt;li&gt;RX&lt;/li&gt;
&lt;li&gt;VCC&lt;/li&gt;
&lt;li&gt;TX&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For the bracket to hold the serial port I printed this
&lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/src/content/images/2024/3dp/bracket.stl&quot;&gt;3D Model&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Place the Serial module on the 3D printed bracket.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/3dp/img1.png&quot; alt=&quot;Bracket&quot; /&gt;&lt;/p&gt;
&lt;p&gt;And mount it on the case:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/3dp/img7.png&quot; alt=&quot;Case with bracket1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/3dp/img4.png&quot; alt=&quot;Case with bracket2&quot; /&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>3D Printing updates</title>
<link href="https://www.0ink.net/posts/2024/2024-07-15-3d-printing.html"></link>
<id>urn:uuid:10d11b90-41bc-7d53-973c-b8986fdf8f2b</id>
<updated>2024-11-22T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[A quick guide to 3D printing materials]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Background&quot;&gt;Background&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#PLA+%28Polylactic+Acid%29&quot;&gt;PLA (Polylactic Acid)&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#What+do+you+use+PLA+for%3F&quot;&gt;What do you use PLA for?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#What+are+the+properties+of+PLA%3F&quot;&gt;What are the properties of PLA?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#PETG+%28Polyethylene+Terephthalate+Glycol%29&quot;&gt;PETG (Polyethylene Terephthalate Glycol)&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#What+do+you+use+PETG+for%3F&quot;&gt;What do you use PETG for?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#What+are+the+properties+of+PETG%3F&quot;&gt;What are the properties of PETG?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#For+which+projects+do+I+use+PETG%3F&quot;&gt;For which projects do I use PETG?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#TPU+%28Thermoplastic+Polyurethane%29&quot;&gt;TPU (Thermoplastic Polyurethane)&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#What+do+you+use+TPU+for%3F&quot;&gt;What do you use TPU for?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#What+are+the+properties+of+TPU%3F&quot;&gt;What are the properties of TPU?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#For+which+projects+do+I+use+TPU%3F&quot;&gt;For which projects do I use TPU?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#ABS+%28Acrylonitrile+Butadiene+Styrene%29&quot;&gt;ABS (Acrylonitrile Butadiene Styrene)&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#What+do+you+use+ABS+for%3F&quot;&gt;What do you use ABS for?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#What+are+the+properties+of+ABS%3F&quot;&gt;What are the properties of ABS?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#For+which+projects+do+I+use+ABS%3F&quot;&gt;For which projects do I use ABS?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#ASA+%28Acrylonitrile+Styrene+Acrylate%29&quot;&gt;ASA (Acrylonitrile Styrene Acrylate)&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#What+do+you+use+ASA+for%3F&quot;&gt;What do you use ASA for?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#What+are+the+properties+of+ASA%3F&quot;&gt;What are the properties of ASA?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#For+which+projects+do+I+use+ASA%3F&quot;&gt;For which projects do I use ASA?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#PA-CF+%28Polyamide+Carbon+Fiber%29&quot;&gt;PA-CF (Polyamide Carbon Fiber)&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#What+do+you+use+PA-CF+for%3F&quot;&gt;What do you use PA-CF for?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#What+are+the+properties+of+PA-CF%3F&quot;&gt;What are the properties of PA-CF?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#For+which+projects+do+I+use+PA-CF%3F&quot;&gt;For which projects do I use PA-CF?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/3dprt.png&quot; alt=&quot;3D Printer&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;Background&quot; name=&quot;Background&quot;&gt;Background&lt;/h2&gt;
&lt;p&gt;So I am back at playing with 3D printing.&lt;/p&gt;
&lt;p&gt;What happened is that I am trying to add a serial port to my &lt;a href=&quot;https://www.qnap.com/en/product/ts-251d&quot;&gt;QNAP TS-251D&lt;/a&gt; NAS which uses a
special &lt;em&gt;&amp;quot;flat&amp;quot;&lt;/em&gt; low-profile bracket.  Since there a none of those available in the open market, I
decided to 3D print a bracket.&lt;/p&gt;
&lt;p&gt;I was able to find a 3D model for a &lt;a href=&quot;https://www.printables.com/en/model/201077-qnap-ts-563-pci-bracket-for-amd-r7-250-video-card-/files&quot;&gt;QNAP TS-563 PCI Bracket for AMD R7 250 Video Card PCIe&lt;/a&gt;,
which I modified to allow for a DB9 serial port opening.&lt;/p&gt;
&lt;p&gt;Also found a local &lt;a href=&quot;https://3d-demand.nl/product/3d-print-service/&quot;&gt;3D Priting service&lt;/a&gt; which lets me upload a model, select different config
settings.  They would then (for fee) print it and ship it to you.&lt;/p&gt;
&lt;p&gt;So far the service is fairly quick and seems reliable so far.&lt;/p&gt;
&lt;p&gt;The challenge has been the first few tries did not provide enough structural integrity for it to work.
Specifically, it would bend when pushing in the serial port connector so it wouldn&#039;t set properly.&lt;/p&gt;
&lt;p&gt;Currently I am trying modifying the shape so it bends less easily with a stronger material.
Also relocated the port location so it is closer to the edge so it benefits of the support
provided by the mounting screw.&lt;/p&gt;
&lt;p&gt;The 3D Printing service I am using let&#039;s you choose the printing material among the following:&lt;/p&gt;
&lt;h2 id=&quot;PLA+%28Polylactic+Acid%29&quot; name=&quot;PLA+%28Polylactic+Acid%29&quot;&gt;PLA (Polylactic Acid)&lt;/h2&gt;
&lt;h3 id=&quot;What+do+you+use+PLA+for%3F&quot; name=&quot;What+do+you+use+PLA+for%3F&quot;&gt;What do you use PLA for?&lt;/h3&gt;
&lt;p&gt;PLA is ideal for projects with limited structural requirements, such as decorative objects
or small gadgets.&lt;/p&gt;
&lt;h3 id=&quot;What+are+the+properties+of+PLA%3F&quot; name=&quot;What+are+the+properties+of+PLA%3F&quot;&gt;What are the properties of PLA?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Biodegradable and compostable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Made from renewable resources such as corn starch or sugar cane.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Has a relatively low melting temperature.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Possesses a glossy surface, which makes it attractive for visual applications.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For which projects do I use PLA?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Beginner projects, decorative objects, prototypes that do not require high heat or impact resistance.&lt;/p&gt;
&lt;h2 id=&quot;PETG+%28Polyethylene+Terephthalate+Glycol%29&quot; name=&quot;PETG+%28Polyethylene+Terephthalate+Glycol%29&quot;&gt;PETG (Polyethylene Terephthalate Glycol)&lt;/h2&gt;
&lt;h3 id=&quot;What+do+you+use+PETG+for%3F&quot; name=&quot;What+do+you+use+PETG+for%3F&quot;&gt;What do you use PETG for?&lt;/h3&gt;
&lt;p&gt;PETG is suitable for projects that require durability, such as functional parts or waterproof objects.&lt;/p&gt;
&lt;h3 id=&quot;What+are+the+properties+of+PETG%3F&quot; name=&quot;What+are+the+properties+of+PETG%3F&quot;&gt;What are the properties of PETG?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Strong and reliable for long-term use.&lt;/li&gt;
&lt;li&gt;Resistant to many chemicals.&lt;/li&gt;
&lt;li&gt;Suitable for humid environments.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;For+which+projects+do+I+use+PETG%3F&quot; name=&quot;For+which+projects+do+I+use+PETG%3F&quot;&gt;For which projects do I use PETG?&lt;/h3&gt;
&lt;p&gt;Functional parts, parts that are used outdoors, or objects that require some flexibility and strength.&lt;/p&gt;
&lt;h2 id=&quot;TPU+%28Thermoplastic+Polyurethane%29&quot; name=&quot;TPU+%28Thermoplastic+Polyurethane%29&quot;&gt;TPU (Thermoplastic Polyurethane)&lt;/h2&gt;
&lt;h3 id=&quot;What+do+you+use+TPU+for%3F&quot; name=&quot;What+do+you+use+TPU+for%3F&quot;&gt;What do you use TPU for?&lt;/h3&gt;
&lt;p&gt;TPU is suitable for flexible and durable projects, such as phone cases, flexible hinges or
shock-absorbing parts.&lt;/p&gt;
&lt;h3 id=&quot;What+are+the+properties+of+TPU%3F&quot; name=&quot;What+are+the+properties+of+TPU%3F&quot;&gt;What are the properties of TPU?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Ideal for flexible objects.&lt;/li&gt;
&lt;li&gt;Excellent wear resistance.&lt;/li&gt;
&lt;li&gt;Provides good protection against shocks.&lt;/li&gt;
&lt;li&gt;Resists oil, grease, and more.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;For+which+projects+do+I+use+TPU%3F&quot; name=&quot;For+which+projects+do+I+use+TPU%3F&quot;&gt;For which projects do I use TPU?&lt;/h3&gt;
&lt;p&gt;Phone cases, hinges, shoe soles, and other products that require flexibility and durability.&lt;/p&gt;
&lt;h2 id=&quot;ABS+%28Acrylonitrile+Butadiene+Styrene%29&quot; name=&quot;ABS+%28Acrylonitrile+Butadiene+Styrene%29&quot;&gt;ABS (Acrylonitrile Butadiene Styrene)&lt;/h2&gt;
&lt;h3 id=&quot;What+do+you+use+ABS+for%3F&quot; name=&quot;What+do+you+use+ABS+for%3F&quot;&gt;What do you use ABS for?&lt;/h3&gt;
&lt;p&gt;ABS is used for projects that require strength and heat resistance, such as mechanical parts or
housings.&lt;/p&gt;
&lt;h3 id=&quot;What+are+the+properties+of+ABS%3F&quot; name=&quot;What+are+the+properties+of+ABS%3F&quot;&gt;What are the properties of ABS?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;ABS is strong and tough, suitable for solid objects.&lt;/li&gt;
&lt;li&gt;Can withstand higher temperatures than many other plastics.&lt;/li&gt;
&lt;li&gt;Can be sanded, drilled and painted for various finishes.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;For+which+projects+do+I+use+ABS%3F&quot; name=&quot;For+which+projects+do+I+use+ABS%3F&quot;&gt;For which projects do I use ABS?&lt;/h3&gt;
&lt;p&gt;Functional prototypes, tools, and parts for the automotive industry&lt;/p&gt;
&lt;h2 id=&quot;ASA+%28Acrylonitrile+Styrene+Acrylate%29&quot; name=&quot;ASA+%28Acrylonitrile+Styrene+Acrylate%29&quot;&gt;ASA (Acrylonitrile Styrene Acrylate)&lt;/h2&gt;
&lt;h3 id=&quot;What+do+you+use+ASA+for%3F&quot; name=&quot;What+do+you+use+ASA+for%3F&quot;&gt;What do you use ASA for?&lt;/h3&gt;
&lt;p&gt;ASA is ideal for outdoor applications that require UV resistance and weather resistance,
such as automotive parts or outdoor decorations.&lt;/p&gt;
&lt;h3 id=&quot;What+are+the+properties+of+ASA%3F&quot; name=&quot;What+are+the+properties+of+ASA%3F&quot;&gt;What are the properties of ASA?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;ASA does not fade under exposure to sunlight.&lt;/li&gt;
&lt;li&gt;Resistant to water and many weather influences.&lt;/li&gt;
&lt;li&gt;Provides a strong and durable finish.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;For+which+projects+do+I+use+ASA%3F&quot; name=&quot;For+which+projects+do+I+use+ASA%3F&quot;&gt;For which projects do I use ASA?&lt;/h3&gt;
&lt;p&gt;Outdoor projects, parts for cars or garden furniture, and any object exposed to the elements.&lt;/p&gt;
&lt;h2 id=&quot;PA-CF+%28Polyamide+Carbon+Fiber%29&quot; name=&quot;PA-CF+%28Polyamide+Carbon+Fiber%29&quot;&gt;PA-CF (Polyamide Carbon Fiber)&lt;/h2&gt;
&lt;h3 id=&quot;What+do+you+use+PA-CF+for%3F&quot; name=&quot;What+do+you+use+PA-CF+for%3F&quot;&gt;What do you use PA-CF for?&lt;/h3&gt;
&lt;p&gt;PA-CF is used for highly load-bearing applications that require stiffness and wear resistance,
such as industrial tools or parts with high mechanical loads.&lt;/p&gt;
&lt;h3 id=&quot;What+are+the+properties+of+PA-CF%3F&quot; name=&quot;What+are+the+properties+of+PA-CF%3F&quot;&gt;What are the properties of PA-CF?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;PA-CF provides exceptional stiffness for precision parts.&lt;/li&gt;
&lt;li&gt;Highly resistant to wear, ideal for moving parts.&lt;/li&gt;
&lt;li&gt;Retains properties at higher temperatures.&lt;/li&gt;
&lt;li&gt;Good resistance to many chemicals and solvents.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;For+which+projects+do+I+use+PA-CF%3F&quot; name=&quot;For+which+projects+do+I+use+PA-CF%3F&quot;&gt;For which projects do I use PA-CF?&lt;/h3&gt;
&lt;p&gt;Aerospace, automotive, and mechanical engineering, where parts must be able to withstand high
temperatures and wear.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;For the bracket I am using &lt;strong&gt;PA-CF&lt;/strong&gt; as it seems to be the most rigid material I have tried so
far.&lt;/p&gt;</content>
</entry>
<entry>
<title>UEFI boot on Raspberry PI</title>
<link href="https://www.0ink.net/posts/2024/2024-07-01-pi-uefi-boot.html"></link>
<id>urn:uuid:fd39a4ef-0348-3309-2ece-c8300cfafef6</id>
<updated>2024-11-22T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Introduction
Prepare SD card
Copy UEFI
UEFI boot
Adding grub
...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Introduction&quot;&gt;Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Prepare+SD+card&quot;&gt;Prepare SD card&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Copy+UEFI&quot;&gt;Copy UEFI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#UEFI+boot&quot;&gt;UEFI boot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Adding+grub&quot;&gt;Adding grub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Installing+an+operating+system&quot;&gt;Installing an operating system&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/uefi_forum.png&quot; alt=&quot;UEFI logo&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;Introduction&quot; name=&quot;Introduction&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;This recipe is to run UEFI boot on the raspberry pi.  This is possible because
there is an opensource UEFI firmware that has been ported to the Raspberry Pi.&lt;/p&gt;
&lt;p&gt;I have tested the fimrware from &lt;a href=&quot;https://github.com/pftf/RPi3&quot;&gt;pftf/RPi3&lt;/a&gt;.
Which says:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The use of this firmware can &lt;strong&gt;greatly simplify&lt;/strong&gt; the installation of generic Linux
distributions such as &lt;a href=&quot;https://pete.akeo.ie/2019/07/installing-debian-arm64-on-raspberry-pi.html&quot;&gt;Debian&lt;/a&gt;
or &lt;a href=&quot;https://github.com/tianocore/edk2-platforms/blob/master/Platform/RaspberryPi/RPi3/Systems.md#Ubuntu&quot;&gt;Ubuntu&lt;/a&gt;
as well as &lt;a href=&quot;https://www.worproject.ml/&quot;&gt;Windows 10&lt;/a&gt; (in regular GUI mode, not IoT mode),
straight from their ISO images.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is an installable build of the official
&lt;a href=&quot;https://github.com/tianocore/edk2-platforms/tree/master/Platform/RaspberryPi/RPi3&quot;&gt;EDK2 Raspberry Pi 3 UEFI firmware&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This project states:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This is meant as a generally useful 64-bit ATF + UEFI implementation for
Raspberry Pi variants based on the BCM2837 SoC, which should be good enough for
most kind of UEFI development, as well as for running consummer Operating Systems
in such as Linux, Windows or the BSDs.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Personally, I am using this to load &lt;a href=&quot;https://www.gnu.org/software/grub/&quot;&gt;grub&lt;/a&gt; and from there boot &lt;a href=&quot;https://alpinelinux.org/&quot;&gt;AlpineLinx&lt;/a&gt;.
This allows me to have multiple versions of &lt;a href=&quot;https://alpinelinux.org/&quot;&gt;AlpineLinux&lt;/a&gt; in one SD card, and
use the &lt;a href=&quot;https://www.gnu.org/software/grub/&quot;&gt;grub&lt;/a&gt; menu to select the version to boot.&lt;/p&gt;
&lt;p&gt;This makes it possible to perform and rollback &lt;a href=&quot;https://alpinelinux.org/&quot;&gt;AlpineLinux&lt;/a&gt; upgrades without
having to reflash the SD card.&lt;/p&gt;
&lt;h2 id=&quot;Prepare+SD+card&quot; name=&quot;Prepare+SD+card&quot;&gt;Prepare SD card&lt;/h2&gt;
&lt;p&gt;First step is to create an SD card t o be used (or uSB drive), with a &lt;code&gt;MBR&lt;/code&gt; partition
table.  Create a partition of of type &lt;code&gt;0x0c&lt;/code&gt; (&lt;code&gt;FAT32 LBA&lt;/code&gt;) or &lt;code&gt;0x0e&lt;/code&gt;
(&lt;code&gt;FAT16 LBA&lt;/code&gt;). Then format this partition to &lt;code&gt;FAT32&lt;/code&gt;.  &lt;em&gt;NOTE:&lt;/em&gt; Actually I found
an &lt;a href=&quot;https://pete.akeo.ie/2019/07/installing-debian-arm64-on-raspberry-pi.html&quot;&gt;article&lt;/a&gt;
stating that the partition type should be &lt;code&gt;0x0c&lt;/code&gt; and the filesystem should be
&lt;code&gt;FAT16&lt;/code&gt;.  I have tested this configuration, which limits the boot partition to 2GB.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Do not try to use &lt;code&gt;GPT&lt;/code&gt; for the partition scheme or &lt;code&gt;0xef&lt;/code&gt; (&lt;code&gt;EFI System Partition&lt;/code&gt;)  for the type, as these are unsupported by the CPU-embedded bootloader&lt;/p&gt;
&lt;h2 id=&quot;Copy+UEFI&quot; name=&quot;Copy+UEFI&quot;&gt;Copy UEFI&lt;/h2&gt;
&lt;p&gt;Retrieve the UEFI Firmware from &lt;a href=&quot;https://github.com/pftf/RPi3&quot;&gt;here&lt;/a&gt; and extract its files on
the partition created in the previous step.&lt;/p&gt;
&lt;p&gt;This firmware supports Pi revisions based on the BCM2837 SoC:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Raspberry Pi 2B v1.2 (older versions are &lt;em&gt;not&lt;/em&gt; compatible)&lt;/li&gt;
&lt;li&gt;Raspberry Pi 3A+&lt;/li&gt;
&lt;li&gt;Raspberry Pi 3B&lt;/li&gt;
&lt;li&gt;Raspberry Pi 3B+&lt;/li&gt;
&lt;li&gt;Raspberry Pi CM3&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I found additional versions however, these seem to have some hardware
compatibility issues:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/pftf/RPi4&quot;&gt;Raspberry Pi 4 UEFI support&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/worproject/rpi5-uefi&quot;&gt;Raspberry Pi 5&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;UEFI+boot&quot; name=&quot;UEFI+boot&quot;&gt;UEFI boot&lt;/h2&gt;
&lt;p&gt;Insert the SD card/plug the USB drive and power up your Raspberry Pi. You should see a
multicoloured screen (which indicates that the CPU-embedded bootloader is reading the
data from the SD/USB partition) and then the Raspberry Pi black and white logo once the
UEFI firmware is ready.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/pi-uefi-Screenshot1.png&quot; alt=&quot;Screenshot1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;At this stage, you can press &lt;kbd&gt;Esc&lt;/kbd&gt; to enter the firmware setup, &lt;kbd&gt;F1&lt;/kbd&gt;
to launch the UEFI Shell, or, provided you also have copied an UEFI bootloader in
&lt;code&gt;efi/boot/bootaa64.efi&lt;/code&gt;, you can let the UEFI system run that (which it should do by
default if no action is taken).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/pi-uefi-Screenshot2.png&quot; alt=&quot;Screenshot2&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;Adding+grub&quot; name=&quot;Adding+grub&quot;&gt;Adding grub&lt;/h2&gt;
&lt;p&gt;At this point you should have an UEFI boot capable Raspberry PI.  Now you need
to get &lt;a href=&quot;https://www.gnu.org/software/grub/&quot;&gt;grub&lt;/a&gt; bootloader for the AArch64 architecture.  I got mine from
the debian &lt;a href=&quot;https://cdimage.debian.org/debian-cd/current/arm64/iso-cd/&quot;&gt;netinstall iso&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For convenience the needed files can be found
&lt;a href=&quot;/images/2024/uefi-pi/grub-aarch64.zip&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As it is UEFI, you can simply copy the files to the boot partition created earlier.&lt;/p&gt;
&lt;p&gt;You should create a boot menu and place it in the boot partition as &lt;code&gt;boot/grub/grub.cfg&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is an example &lt;code&gt;grub.cfg&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;set timeout=10
set default=alpine-rpi-3.19.1,rpi

menuentry &quot;alpine-rpi-3.19.1,rpi (2023-12-27)&quot; --id alpine-rpi-3.19.1,rpi {
  echo &quot;Booting alpine-rpi-3.19.1,rpi ...&quot;
  linux /boot/vmlinuz-rpi modloop=/boot/modloop-rpi modules=loop,squashfs,sd-mod,usb-storage quiet console=tty1 
  initrd /boot/initramfs-rpi
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Installing+an+operating+system&quot; name=&quot;Installing+an+operating+system&quot;&gt;Installing an operating system&lt;/h2&gt;
&lt;p&gt;At this point you can boot your Linux based operating system as you would with any
other &lt;a href=&quot;https://www.gnu.org/software/grub/&quot;&gt;grub&lt;/a&gt; based installation.&lt;/p&gt;</content>
</entry>
<entry>
<title>Ansible Best Practices</title>
<link href="https://www.0ink.net/posts/2024/2024-06-15-ansible-best.html"></link>
<id>urn:uuid:8c35bc1f-ba99-52a4-48fe-8708ba4d9430</id>
<updated>2024-11-22T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
This is a conversion from a presentation/pdf
by Tim Appnel.
I attached a copy here
too.
Roles and Modules
...]]></summary>
<content type="html">&lt;p&gt;&lt;img src=&quot;/images/2024/ansible_logo2.png&quot; alt=&quot;ansible logo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This is a conversion from a &lt;a href=&quot;https://www.ansible.com/hubfs/2018_Content/Ansible%20Best%20Practices%20Roles%20&amp;amp;%20Modules.pdf&quot;&gt;presentation/pdf&lt;/a&gt;
by Tim Appnel.&lt;/p&gt;
&lt;p&gt;I attached a copy &lt;a href=&quot;/images/2024/Ansible_Best_Pratices_Roles_and_Modules.pdf&quot;&gt;here&lt;/a&gt;
too.&lt;/p&gt;
&lt;h2 id=&quot;Roles+and+Modules&quot; name=&quot;Roles+and+Modules&quot;&gt;Roles and Modules&lt;/h2&gt;
&lt;h3 id=&quot;Complexity+Kills+Productivity&quot; name=&quot;Complexity+Kills+Productivity&quot;&gt;Complexity Kills Productivity&lt;/h3&gt;
&lt;p&gt;That&#039;s not just a marketing slogan. We really mean it and
believe that. We strive to reduce complexity in how we&#039;ve
designed Ansible tools and encourage you to do the
same. Strive for simplification in what you automate.&lt;/p&gt;
&lt;h3 id=&quot;Optimize+for+Readbility&quot; name=&quot;Optimize+for+Readbility&quot;&gt;Optimize for Readbility&lt;/h3&gt;
&lt;p&gt;If done properly, it can be the documentation of your
workflow automation.&lt;/p&gt;
&lt;h3 id=&quot;Think+Declaratively&quot; name=&quot;Think+Declaratively&quot;&gt;Think Declaratively&lt;/h3&gt;
&lt;p&gt;Ansible is a desired state engine by design. If you&#039;re
trying to &amp;quot;write code&amp;quot; in your plays and roles, you&#039;re
setting yourself up for failure. Our YAML-based
playbooks were never meant to be for programming.&lt;/p&gt;
&lt;h3 id=&quot;Roles+%2B+Modules&quot; name=&quot;Roles+%2B+Modules&quot;&gt;Roles + Modules&lt;/h3&gt;
&lt;p&gt;Use the right tool for the job:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Roles&lt;/th&gt;
&lt;th&gt;Modules&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Self-contained portable units of Ansible automation&lt;/td&gt;
&lt;td&gt;Small &lt;em&gt;programs&lt;/em&gt; that perform actions on remote hosts or on their behalf&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expressed in YAML and bundled with associated assets&lt;/td&gt;
&lt;td&gt;Expressed as code&lt;br&gt;- i.e. Python, PowerShell&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decoupled from assumptions made by plays&lt;/td&gt;
&lt;td&gt;Called by an Ansible tas&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Modules do all of the heavy lifting in Ansible&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;Roles&quot; name=&quot;Roles&quot;&gt;Roles&lt;/h2&gt;
&lt;h3 id=&quot;Roles+are+Ansible+Content&quot; name=&quot;Roles+are+Ansible+Content&quot;&gt;Roles are Ansible Content&lt;/h3&gt;
&lt;p&gt;The same best practices for your plays still apply:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use native YAML syntax&lt;/li&gt;
&lt;li&gt;Version control your Ansible content&lt;/li&gt;
&lt;li&gt;Use command modules sparingly&lt;/li&gt;
&lt;li&gt;Always seek out a module first&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Name&lt;/em&gt; your plays, blocks and tasks&lt;/li&gt;
&lt;li&gt;Use human meaningful names with variables, hosts, etc.&lt;/li&gt;
&lt;li&gt;Clean up your debugging messages&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Role+Design&quot; name=&quot;Role+Design&quot;&gt;Role Design&lt;/h3&gt;
&lt;p&gt;Keep the purpose and function of a role self-contained and
focused to do one thing well&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Think about the full life-cycle of a service, microservice or
container — not a whole stack or environment&lt;/li&gt;
&lt;li&gt;Keep provisioning separate from configuration and app
deployment&lt;/li&gt;
&lt;li&gt;Roles are not classes or object or libraries - those are
programming constructs&lt;/li&gt;
&lt;li&gt;Keep roles loosely-coupled - limit hard dependencies on other
roles or external variables&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;EXHIBIT+A&quot; name=&quot;EXHIBIT+A&quot;&gt;EXHIBIT A&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# blackbox_role_playbook.yml
---
- hosts: all
  roles:
  - umbrella_corp_stack&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;EXHIBIT+B&quot; name=&quot;EXHIBIT+B&quot;&gt;EXHIBIT B&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# componentized_roles_playbook.yml
---
- hosts: localhost
  roles:
  - azure_provisioner
- hosts: all
  roles:
  - system_security
- hosts: webservers
  roles:
  - python_common
  - python_django
  - nginx_uwsgi
  - racoon_app
- hosts: databases
  roles:
  - pgsql-replication&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Maximize your role design for portability and reuse&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use ansible-galaxy to install your roles&lt;/li&gt;
&lt;li&gt;Use a roles files (i.e. requirements.yml) to manifest your project roles&lt;/li&gt;
&lt;li&gt;When using a shared role always declare a specific version such as a tag or commit&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# requirements.yml
---
- src: nginxinc.nginx
  version: 0.8.0
- src: samdoran.pgsql-replication
  version: b5013e6
- src: geerlingguy.firewall
  version: 2.4.&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Role+Usability&quot; name=&quot;Role+Usability&quot;&gt;Role Usability&lt;/h3&gt;
&lt;p&gt;Roles should run with as few, if any, parameter variables as
possible&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Convention_over_configuration&quot;&gt;Practice convention over configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Provide sane defaults&lt;/li&gt;
&lt;li&gt;Use variable parameters to modify default behaviour&lt;/li&gt;
&lt;li&gt;Easier to develop, test and use quickly &amp;amp; securely&lt;/li&gt;
&lt;li&gt;A role should always be more than a single task file&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;EXHIBIT+A&quot; name=&quot;EXHIBIT+A&quot;&gt;EXHIBIT A&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# defaults_no_playbook.yml
---
- hosts: webservers
  roles:
- role: apache_simple
  apache_http_port: 80
  apache_doc_root: /var/www/html
  apache_user: apache
  apache_group: apache
- role: apache_simple
  apache_http_port: 8080
  apache_doc_root: /www/example.com
  apache_user: apache
  apache_group: apache&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;EXHIBIT+B&quot; name=&quot;EXHIBIT+B&quot;&gt;EXHIBIT B&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# defaults_yes_playbook.yml
---
- hosts: webservers
  roles:
- role: apache_simple
- role: apache_simple
  apache_http_port: 8080
  apache_doc_root: /www/example.com&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# default/main.yml
---
apache_http_port: 80
apache_doc_root: /var/www/html
apache_user: apache
apache_group: apache&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use variables in your roles appropriately&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;defaults/ are easy to override and most commonly used to
modify behavior
&lt;ul&gt;
&lt;li&gt;i.e. port number or default user
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# default/main.yml
---
apache_http_port: 80
apache_doc_root: /var/www/html
apache_user: apache
apache_group: apache&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;vars/ are used by the role and not likely to be changed
&lt;ul&gt;
&lt;li&gt;i.e. a list of packages, lookup table of machine images by region
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;
# vars/main.yml
---
apache_packages:
redhat:&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;httpd&lt;/li&gt;
&lt;li&gt;mod_wsgi
debian:&lt;/li&gt;
&lt;li&gt;apache2&lt;/li&gt;
&lt;li&gt;libapache2-mod-wsgi
&lt;pre&gt;&lt;code&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Automate the testing of your roles&lt;/p&gt;
&lt;p&gt;Use molecule, a testing framework designed to aid in the
development and testing of Ansible Roles.&lt;/p&gt;
&lt;p&gt;Initially developed by the community, led by John Dewey of Cisco, and
adopted by Red Hat as an official Ansible project.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/ansible/molecule&quot;&gt;https://github.com/ansible/molecule&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Use ansible-lint, a command-line static analysis tool that checks
playbooks and roles for identifying behaviour that could be improved.&lt;/p&gt;
&lt;p&gt;Initially developed by Will Thames and recently adopted by Red Hat as
an official Ansible project.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/ansible/ansible-lint&quot;&gt;https://github.com/ansible/ansible-lint&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;HINT: ansible-lint can be run as part of your Molecule test runs.&lt;/p&gt;
&lt;h3 id=&quot;Role+Readability&quot; name=&quot;Role+Readability&quot;&gt;Role Readability&lt;/h3&gt;
&lt;p&gt;Still using command modules a lot?&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: check cert
  shell: certify --list --name={{ cert_name }} --cert_store={{ cert_store }} | grep &quot;{{ cert_name }}&quot;
  register: check_output
- name: create cert
  command: certify --create --user=chris --name={{ cert_name }} --cert_store={{ cert_store }}
  when: check_output.stdout.find(cert_name) != -1
  register: create_output
- name: sign cert
  command: certify --sign --name={{ cert_name }} --cert_store={{ cert_store }}
  when: create_output.stdout.find(&quot;created&quot;) != -1&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Develop your own module&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: create and sign cert
  certify:
    state: present
    sign: yes
    user: chris
    name: &quot;{{ cert_name }}&quot;
    cert_store: &quot;{{ cert_store }}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Modules&quot; name=&quot;Modules&quot;&gt;Modules&lt;/h2&gt;
&lt;h3 id=&quot;Module+Design&quot; name=&quot;Module+Design&quot;&gt;Module Design&lt;/h3&gt;
&lt;p&gt;Good modules are user-centric&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Modules are how Ansible balances simple and powerful&lt;/li&gt;
&lt;li&gt;They implement common automation tasks for a user&lt;/li&gt;
&lt;li&gt;They make easy tasks easier and complicated tasks possible&lt;/li&gt;
&lt;li&gt;They abstract users from having to know the details to get things done&lt;/li&gt;
&lt;li&gt;They are not one-to-one mapping of an API or command line tool interface
&lt;ul&gt;
&lt;li&gt;This is why you should not auto-generate your modules&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;They are not monolithic &lt;em&gt;does everything&lt;/em&gt; modules that are hard to
understand and complicated to use correctly&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Module+Implementation&quot; name=&quot;Module+Implementation&quot;&gt;Module Implementation&lt;/h3&gt;
&lt;p&gt;Making the powerful simple starts with the implementation&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No side-effects with multiple runs&lt;/li&gt;
&lt;li&gt;Err on the side of safety
&lt;ul&gt;
&lt;li&gt;i.e. use a temporary file and atomic_move when writing to a file&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Fail fast -- immediately detect and report failure conditions&lt;/li&gt;
&lt;li&gt;Support check mode&lt;/li&gt;
&lt;li&gt;Minimal use of dependencies&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Module+Interface&quot; name=&quot;Module+Interface&quot;&gt;Module Interface&lt;/h3&gt;
&lt;p&gt;Modules should provide a predictable user interface&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Think desired state, think declaratively&lt;/li&gt;
&lt;li&gt;Avoid action or command parameters&lt;/li&gt;
&lt;li&gt;Keep parameters focused and narrowly defined - refrain from
parameters that take complex data structures&lt;/li&gt;
&lt;li&gt;Parameter names should be in lowercase and use underscores:
&lt;ul&gt;
&lt;li&gt;update_cache # YES&lt;/li&gt;
&lt;li&gt;UpdateCache # NO&lt;/li&gt;
&lt;li&gt;updateCache # NO&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Normalize common parameter names with other modules such
as:
&lt;ul&gt;
&lt;li&gt;name&lt;/li&gt;
&lt;li&gt;state&lt;/li&gt;
&lt;li&gt;dest&lt;/li&gt;
&lt;li&gt;src&lt;/li&gt;
&lt;li&gt;path&lt;/li&gt;
&lt;li&gt;username&lt;/li&gt;
&lt;li&gt;password&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Modules+in+the+wild&quot; name=&quot;Modules+in+the+wild&quot;&gt;Modules in the wild&lt;/h3&gt;
&lt;p&gt;For your consideration...&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;broken_link&quot;&gt;kubernetes&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;monolithic and requires expert knowledge of k8s&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/ansible/ansible-kubernetes-modules/tree/master/library&quot;&gt;ansible-kubernetes-modules&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;fine grained API mapping that is autogenerated&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.ansible.com/ansible/latest/collections/kubernetes/core/k8s_module.html#ansible-collections-kubernetes-core-k8s-module&quot;&gt;k8s&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;better implementation but complex parameters abound and expert knowledge
still required&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.ansible.com/ansible/latest/collections/kubernetes/core/k8s_scale_module.html#ansible-collections-kubernetes-core-k8s-scale-module&quot;&gt;k8s_scale&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;more focused on a specific task — more of this please&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Module+Responses&quot; name=&quot;Module+Responses&quot;&gt;Module Responses&lt;/h3&gt;
&lt;p&gt;Provide informative and consistent responses&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Be consistent in what you return&lt;/li&gt;
&lt;li&gt;Make response data reusable by a play or role&lt;/li&gt;
&lt;li&gt;Return only relevant output — no logs files please&lt;/li&gt;
&lt;li&gt;Accurately report changed status&lt;/li&gt;
&lt;li&gt;Support diff mode if applicable - and return the diff conditionally&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Module+Exception+Handling&quot; name=&quot;Module+Exception+Handling&quot;&gt;Module Exception Handling&lt;/h3&gt;
&lt;p&gt;Handle errors gracefully and predictably&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Apply defensive programming&lt;/li&gt;
&lt;li&gt;Fail fast — validate upfront and use the built-in argument spec function&lt;/li&gt;
&lt;li&gt;Fail predictably and informatively when errors happen&lt;/li&gt;
&lt;li&gt;Avoid catch all exceptions&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Module+Implementation&quot; name=&quot;Module+Implementation&quot;&gt;Module Implementation&lt;/h3&gt;
&lt;p&gt;Don&#039;t reinvent the weheel&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Make use of &lt;code&gt;module_utils/&lt;/code&gt; -- they’re your friends
&lt;ul&gt;
&lt;li&gt;basic.py&lt;/li&gt;
&lt;li&gt;api.py&lt;/li&gt;
&lt;li&gt;facts/&lt;/li&gt;
&lt;li&gt;urls.py&lt;/li&gt;
&lt;li&gt;six.py&lt;/li&gt;
&lt;li&gt;noteworthy others: ec2.py, docker.py, database.py, mysql.py,
powershell.ps1 — and many many more!&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Module+Documentation&quot; name=&quot;Module+Documentation&quot;&gt;Module Documentation&lt;/h3&gt;
&lt;p&gt;Documentation is a requirement&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Examples should include the most common and real world usage&lt;/li&gt;
&lt;li&gt;Examples should be in native YAML syntax&lt;/li&gt;
&lt;li&gt;Return responses must be included and described&lt;/li&gt;
&lt;li&gt;Document your dependencies in the requirements section&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Module+Testing&quot; name=&quot;Module+Testing&quot;&gt;Module Testing&lt;/h3&gt;
&lt;p&gt;Test before you commit and push your code&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Utilize the testing tools in &lt;code&gt;ansible/hacking/&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;test-module&lt;/li&gt;
&lt;li&gt;ansible-test sanity &lt;MODULE_NAME&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Create roles and playbooks and to test and verify all your
documented examples
&lt;ul&gt;
&lt;li&gt;molecule&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Test locally - not with the CI/CD system&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;More+Modules+in+the+wild&quot; name=&quot;More+Modules+in+the+wild&quot;&gt;More Modules in the wild&lt;/h3&gt;
&lt;p&gt;For your consideration...&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.ansible.com/ansible/latest/collections/ansible/posix/sysctl_module.html&quot;&gt;sysctl&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;a master class in writing a “best practice” module&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.ansible.com/ansible/latest/collections/ansible/builtin/ping_module.html&quot;&gt;ping&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;the hello world of Ansible&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.ansible.com/ansible/latest/collections/ansible/builtin/cron_module.html&quot;&gt;cron&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;module implementing an interface to a command line tool&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.ansible.com/ansible/latest/collections/ansible/builtin/get_url_module.html&quot;&gt;get_url&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;module implementing an interface to a python library&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Module+and+Action+Plugins&quot; name=&quot;Module+and+Action+Plugins&quot;&gt;Module and Action Plugins&lt;/h3&gt;
&lt;p&gt;Sometimes modules need something more&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Local controller execution of a module entirely possible or required...&lt;/li&gt;
&lt;li&gt;Special setup requirements before the module is dispatched to the host...&lt;/li&gt;
&lt;li&gt;Need to supplement a module with the services of another core module
such as copy and a role won’t cut it...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;An Action Plugin executes on the controller and perform logic before
dispatching a module&lt;/p&gt;
&lt;h2 id=&quot;More+resources&quot; name=&quot;More+resources&quot;&gt;More resources&lt;/h2&gt;
&lt;p&gt;Ansible Developers Guide&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://docs.ansible.com/ansible/devel/dev_guide/index.html&quot;&gt;http://docs.ansible.com/ansible/devel/dev_guide/index.html&lt;/a&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>hoses: enhanced socks5 tools</title>
<link href="https://www.0ink.net/posts/2024/2024-06-01-hoses.html"></link>
<id>urn:uuid:4c0762dc-7b11-74ce-833d-f5a74972cf78</id>
<updated>2024-03-05T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Introduction
Features
Where to find
Examples

...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Introduction&quot;&gt;Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Features&quot;&gt;Features&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Where+to+find&quot;&gt;Where to find&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Examples&quot;&gt;Examples&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Proxy&quot;&gt;Proxy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#netcat+server&quot;&gt;netcat server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#netcat+client&quot;&gt;netcat client&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#stunnel&quot;&gt;stunnel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#SSH+tunnel&quot;&gt;SSH tunnel&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#References&quot;&gt;References&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/hose_64x64.png&quot; alt=&quot;hoses&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;Introduction&quot; name=&quot;Introduction&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/TortugaLabs/hoses&quot;&gt;hose&lt;/a&gt; is a python package that implements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;socks5 proxy with SSL support&lt;/li&gt;
&lt;li&gt;netcat style cli to use the proxy&lt;/li&gt;
&lt;li&gt;simple stunnel implementation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For a project I had, I needed for remote systems to connect back to a central
management system over the Internet.  This provides a simple application level
proxy that implements this connectivity.&lt;/p&gt;
&lt;div&gt;&lt;svg class=&quot;bob&quot; font-family=&quot;arial&quot; font-size=&quot;14&quot; height=&quot;112&quot; width=&quot;672&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;defs&gt;
&lt;marker id=&quot;triangle&quot; markerHeight=&quot;10&quot; markerUnits=&quot;strokeWidth&quot; markerWidth=&quot;10&quot; orient=&quot;auto&quot; refX=&quot;15&quot; refY=&quot;10&quot; viewBox=&quot;0 0 50 20&quot;&gt;
&lt;path d=&quot;M 0 0 L 30 10 L 0 20 z&quot;/&gt;
&lt;/marker&gt;
&lt;/defs&gt;
&lt;style&gt;

    line, path {
      stroke: black;
      stroke-width: 2;
      stroke-opacity: 1;
      fill-opacity: 1;
      stroke-linecap: round;
      stroke-linejoin: miter;
    }
    circle {
      stroke: black;
      stroke-width: 2;
      stroke-opacity: 1;
      fill-opacity: 1;
      stroke-linecap: round;
      stroke-linejoin: miter;
    }
    circle.solid {
      fill:black;
    }
    circle.open {
      fill:transparent;
    }
    tspan.head{
        fill: none;
        stroke: none;
    }
    
&lt;/style&gt;
&lt;path d=&quot; M 336 24 A 4 4 0 0 0 332 28 M 336 24 L 344 24 M 336 24 L 344 24 M 348 28 A 4 4 0 0 0 344 24 M 4 40 L 8 40 M 4 40 L 4 48 M 8 40 L 16 40 M 8 40 L 16 40 L 24 40 M 16 40 L 24 40 L 32 40 M 24 40 L 32 40 L 40 40 M 32 40 L 40 40 L 48 40 M 40 40 L 48 40 L 56 40 M 48 40 L 56 40 L 64 40 M 56 40 L 64 40 L 72 40 M 64 40 L 72 40 L 80 40 M 72 40 L 80 40 L 88 40 M 80 40 L 88 40 L 96 40 M 88 40 L 96 40 M 100 40 L 96 40 M 100 40 L 100 48 M 140 40 L 144 40 M 140 40 L 140 48 M 144 40 L 152 40 M 144 40 L 152 40 L 160 40 M 152 40 L 160 40 L 168 40 M 160 40 L 168 40 L 176 40 M 168 40 L 176 40 L 184 40 M 176 40 L 184 40 L 192 40 M 184 40 L 192 40 L 200 40 M 192 40 L 200 40 M 204 40 L 200 40 M 204 40 L 204 48 M 312 40 A 4 4 0 0 0 308 44 M 312 40 L 320 40 M 312 40 L 320 40 M 324 40 L 320 40 M 356 32 L 356 48 M 356 32 L 356 48 M 444 40 L 448 40 M 444 40 L 444 48 M 448 40 L 456 40 M 448 40 L 456 40 L 464 40 M 456 40 L 464 40 L 472 40 M 464 40 L 472 40 L 480 40 M 472 40 L 480 40 L 488 40 M 480 40 L 488 40 L 496 40 M 488 40 L 496 40 L 504 40 M 496 40 L 504 40 L 512 40 M 504 40 L 512 40 M 516 40 L 512 40 M 516 40 L 516 48 M 564 40 L 568 40 M 564 40 L 564 48 M 568 40 L 576 40 M 568 40 L 576 40 L 584 40 M 576 40 L 584 40 L 592 40 M 584 40 L 592 40 L 600 40 M 592 40 L 600 40 L 608 40 M 600 40 L 608 40 L 616 40 M 608 40 L 616 40 L 624 40 M 616 40 L 624 40 L 632 40 M 624 40 L 632 40 L 640 40 M 632 40 L 640 40 L 648 40 M 640 40 L 648 40 L 656 40 M 648 40 L 656 40 L 664 40 M 656 40 L 664 40 M 668 40 L 664 40 M 668 40 L 668 48 M 4 48 L 4 64 M 4 48 L 4 64 M 100 48 L 100 64 M 100 48 L 100 64 M 140 48 L 140 64 M 140 48 L 140 64 M 204 48 L 204 64 M 204 48 L 204 64 M 280 56 A 4 4 0 0 0 276 60 M 280 56 L 288 56 M 280 56 L 288 56 L 296 56 M 288 56 L 296 56 M 300 56 L 296 56 M 364 52 A 4 4 0 0 0 368 56 L 376 56 M 368 56 L 376 56 L 384 56 M 376 56 L 384 56 M 388 60 A 4 4 0 0 0 384 56 M 444 48 L 444 64 M 444 48 L 444 64 M 516 48 L 516 64 M 516 48 L 516 64 M 564 48 L 564 64 M 564 48 L 564 64 M 668 48 L 668 64 M 668 48 L 668 64 M 4 64 L 4 80 M 4 64 L 4 80 M 100 72 L 100 64 M 100 72 L 104 72 M 100 72 L 100 80 M 104 72 L 112 72 M 104 72 L 112 72 L 120 72 M 112 72 L 120 72 L 128 72 M 120 72 L 128 72 L 136 72 M 128 72 L 136 72 M 140 72 L 140 64 M 140 72 L 136 72 M 140 72 L 140 80 M 204 72 L 204 64 M 204 72 L 208 72 M 204 72 L 204 80 M 208 72 L 216 72 M 208 72 L 216 72 L 224 72 M 216 72 L 224 72 L 232 72 M 224 72 L 232 72 L 240 72 M 232 72 L 240 72 L 248 72 M 240 72 L 248 72 L 268 72 M 248 72 L 268 72 M 256 72 L 268 72 M 268 64 L 268 80 M 268 64 L 268 80 M 396 64 L 396 80 M 396 64 L 396 80 M 408 72 L 396 72 M 408 72 L 416 72 M 408 72 L 416 72 L 424 72 M 416 72 L 424 72 L 432 72 M 424 72 L 432 72 L 440 72 M 432 72 L 440 72 M 444 72 L 444 64 M 444 72 L 440 72 M 444 72 L 444 80 M 516 72 L 516 64 M 516 72 L 520 72 M 516 72 L 516 80 M 520 72 L 528 72 M 520 72 L 528 72 L 536 72 M 528 72 L 536 72 L 544 72 M 536 72 L 544 72 L 552 72 M 544 72 L 552 72 L 560 72 M 552 72 L 560 72 M 564 72 L 564 64 M 564 72 L 560 72 M 564 72 L 564 80 M 668 64 L 668 80 M 668 64 L 668 80 M 4 80 L 4 96 M 4 80 L 4 96 M 100 80 L 100 96 M 100 80 L 100 96 M 140 80 L 140 96 M 140 80 L 140 96 M 204 80 L 204 96 M 204 80 L 204 96 M 268 80 L 268 96 M 268 80 L 268 96 M 396 80 L 396 96 M 396 80 L 396 96 M 444 80 L 444 96 M 444 80 L 444 96 M 516 80 L 516 96 M 516 80 L 516 96 M 564 80 L 564 96 M 564 80 L 564 96 M 668 80 L 668 96 M 668 80 L 668 96 M 4 104 L 4 96 M 4 104 L 8 104 L 16 104 M 8 104 L 16 104 L 24 104 M 16 104 L 24 104 L 32 104 M 24 104 L 32 104 L 40 104 M 32 104 L 40 104 L 48 104 M 40 104 L 48 104 L 56 104 M 48 104 L 56 104 L 64 104 M 56 104 L 64 104 L 72 104 M 64 104 L 72 104 L 80 104 M 72 104 L 80 104 L 88 104 M 80 104 L 88 104 L 96 104 M 88 104 L 96 104 M 100 104 L 100 96 M 100 104 L 96 104 M 140 104 L 140 96 M 140 104 L 144 104 L 152 104 M 144 104 L 152 104 L 160 104 M 152 104 L 160 104 L 168 104 M 160 104 L 168 104 L 176 104 M 168 104 L 176 104 L 184 104 M 176 104 L 184 104 L 192 104 M 184 104 L 192 104 L 200 104 M 192 104 L 200 104 M 204 104 L 204 96 M 204 104 L 200 104 M 276 100 A 4 4 0 0 0 280 104 L 288 104 M 280 104 L 288 104 L 296 104 M 288 104 L 296 104 L 304 104 M 296 104 L 304 104 L 312 104 M 304 104 L 312 104 L 320 104 M 312 104 L 320 104 L 328 104 M 320 104 L 328 104 L 336 104 M 328 104 L 336 104 L 344 104 M 336 104 L 344 104 L 352 104 M 344 104 L 352 104 L 360 104 M 352 104 L 360 104 L 368 104 M 360 104 L 368 104 L 376 104 M 368 104 L 376 104 L 384 104 M 376 104 L 384 104 A 4 4 0 0 0 388 100 M 444 104 L 444 96 M 444 104 L 448 104 L 456 104 M 448 104 L 456 104 L 464 104 M 456 104 L 464 104 L 472 104 M 464 104 L 472 104 L 480 104 M 472 104 L 480 104 L 488 104 M 480 104 L 488 104 L 496 104 M 488 104 L 496 104 L 504 104 M 496 104 L 504 104 L 512 104 M 504 104 L 512 104 M 516 104 L 516 96 M 516 104 L 512 104 M 564 104 L 564 96 M 564 104 L 568 104 L 576 104 M 568 104 L 576 104 L 584 104 M 576 104 L 584 104 L 592 104 M 584 104 L 592 104 L 600 104 M 592 104 L 600 104 L 608 104 M 600 104 L 608 104 L 616 104 M 608 104 L 616 104 L 624 104 M 616 104 L 624 104 L 632 104 M 624 104 L 632 104 L 640 104 M 632 104 L 640 104 L 648 104 M 640 104 L 648 104 L 656 104 M 648 104 L 656 104 L 664 104 M 656 104 L 664 104 M 668 104 L 668 96 M 668 104 L 664 104&quot; fill=&quot;none&quot;/&gt;
&lt;path d=&quot;&quot; fill=&quot;none&quot; stroke-dasharray=&quot;3 3&quot;/&gt;
&lt;text x=&quot;17&quot; y=&quot;60&quot;&gt;
Managment
&lt;/text&gt;
&lt;text x=&quot;153&quot; y=&quot;60&quot;&gt;
SOCKS
&lt;/text&gt;
&lt;text x=&quot;465&quot; y=&quot;60&quot;&gt;
hose
&lt;/text&gt;
&lt;text x=&quot;577&quot; y=&quot;60&quot;&gt;
Management
&lt;/text&gt;
&lt;text x=&quot;297&quot; y=&quot;76&quot;&gt;
Internet
&lt;/text&gt;
&lt;text x=&quot;25&quot; y=&quot;92&quot;&gt;
Server
&lt;/text&gt;
&lt;text x=&quot;153&quot; y=&quot;92&quot;&gt;
Proxy
&lt;/text&gt;
&lt;text x=&quot;457&quot; y=&quot;92&quot;&gt;
netcat
&lt;/text&gt;
&lt;text x=&quot;593&quot; y=&quot;92&quot;&gt;
agents
&lt;/text&gt;
&lt;/svg&gt;
&lt;/div&gt;
&lt;p&gt;Rather than re-inventing the wheel, it makes use of the &lt;a href=&quot;https://docs.python.org/3/library/ssl.html&quot;&gt;python SSL&lt;/a&gt; module
to provide security and certificate based authentication.&lt;/p&gt;
&lt;p&gt;The certificate authentication uses standard X509 certifcates as supported by
&lt;a href=&quot;https://www.openssl.org/&quot;&gt;OpenSSL&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Application proxy uses the &lt;a href=&quot;https://en.wikipedia.org/wiki/SOCKS&quot;&gt;SOCKS&lt;/a&gt;, which is properly defined as &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc1928&quot;&gt;RFC1928&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As functionality goes, it focus mostly in connecting network sockets and in &lt;a href=&quot;https://en.wikipedia.org/wiki/Netcat&quot;&gt;netcat&lt;/a&gt;
functionality piping to running commands.&lt;/p&gt;
&lt;p&gt;Access control can be defined in static files, but also it can be delegated to external
scripts or a REST API server.&lt;/p&gt;
&lt;h2 id=&quot;Features&quot; name=&quot;Features&quot;&gt;Features&lt;/h2&gt;
&lt;p&gt;The following is implemented:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/SOCKS&quot;&gt;SOCKS v5&lt;/a&gt; protocol, with &lt;code&gt;CONNECT&lt;/code&gt; and &lt;code&gt;BIND&lt;/code&gt; commands.  It does &lt;em&gt;not&lt;/em&gt; implement
&lt;code&gt;UDP Associate&lt;/code&gt; commands.  The Authentication handshake is performed, but no Authentication
is implemented.  &lt;a href=&quot;https://github.com/TortugaLabs/hoses&quot;&gt;hoses&lt;/a&gt; expects the connection to be wrapped in a SSL/TLS stream
and clients are authenticated using certificates at that level.&lt;/li&gt;
&lt;li&gt;As mentioned earlier, SSL/TLS is used to secure the application proxy and it is performed
&lt;em&gt;before&lt;/em&gt; &lt;a href=&quot;https://en.wikipedia.org/wiki/SOCKS&quot;&gt;SOCKS&lt;/a&gt; protocol starts running.&lt;/li&gt;
&lt;li&gt;The &lt;a href=&quot;https://en.wikipedia.org/wiki/SOCKS&quot;&gt;SOCKS&lt;/a&gt; protocol is further extended to allow connecting/binding to UNIX sockets.&lt;/li&gt;
&lt;li&gt;Proxy activity is logged in an optional &lt;em&gt;audit.log&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Access control is largely untested, but implemented as either:
&lt;ul&gt;
&lt;li&gt;script/command that runs in the background and accepts JSON requests with client data
and returns &lt;code&gt;allow&lt;/code&gt; or &lt;code&gt;deny&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Or a combination of:
&lt;ul&gt;
&lt;li&gt;static access list&lt;/li&gt;
&lt;li&gt;script that runs with environment variables containing request data and returns an access list&lt;/li&gt;
&lt;li&gt;REST API endpoint that access JSON content and retunrs an access list.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In order to use the application proxy, &lt;a href=&quot;https://en.wikipedia.org/wiki/Netcat&quot;&gt;netcat&lt;/a&gt; type functionality is implemented
with the following features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Connect to remote hosts either directly or via &lt;a href=&quot;https://github.com/TortugaLabs/hoses&quot;&gt;hoses&lt;/a&gt; application proxy.&lt;/li&gt;
&lt;li&gt;Listen to incoming connections and spawn an external program.&lt;/li&gt;
&lt;li&gt;SSL/TLS wrapped connections with optional client certificate validation.&lt;/li&gt;
&lt;li&gt;Connections to TCP ports and UNIX sockets.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To facilitate securing legacy servers, &lt;a href=&quot;https://www.stunnel.org/&quot;&gt;stunnel&lt;/a&gt; functionality is available:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Port forwarding with optional SSL/TLS&lt;/li&gt;
&lt;li&gt;SSL/TLS wiht optional client certificate validation&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Where+to+find&quot; name=&quot;Where+to+find&quot;&gt;Where to find&lt;/h2&gt;
&lt;p&gt;The project is hosted on &lt;a href=&quot;https://github.com/TortugaLabs/hoses&quot;&gt;github&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;Examples&quot; name=&quot;Examples&quot;&gt;Examples&lt;/h2&gt;
&lt;h3 id=&quot;Proxy&quot; name=&quot;Proxy&quot;&gt;Proxy&lt;/h3&gt;
&lt;p&gt;The main usage is as a socks proxy.&lt;/p&gt;
&lt;p&gt;Basic socks5 compatible proxy:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;hoses -S * -P 1080 proxy&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-S *&lt;/code&gt; will listen on all IPv4 and IPv6 addresses.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-P 1080&lt;/code&gt; listen on port 1080&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Enable TLS and listen only on loopback address:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;hoses -S 127.0.0.1 -P 3340 --cert server.crt --key server.key&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Enable TLS and enable client certificate verification:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--cert server.crt&lt;/code&gt; : file path to server certificate&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--key server.key&lt;/code&gt; : file path to corresponding key.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;hoses -S 127.0.0.1 -P 3340 --cert server.crt --key server.key --ca ca.crt&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--ca ca.crt&lt;/code&gt; Certificate file signing client certificates (or the client
certificate itself for self-signed certificates.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;netcat+server&quot; name=&quot;netcat+server&quot;&gt;netcat server&lt;/h3&gt;
&lt;p&gt;It can be used as &lt;code&gt;netcat --listen&lt;/code&gt; replacement.&lt;/p&gt;
&lt;p&gt;Listen on localhost, port 4040 and run a command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;hose listen --exec localhost 4040 &#039;sh -c &quot;python3 eliza.py&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Listen on unix socket on the socks server, and forward
connections to port 22 on remotehost.  And persist bindings.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;hose -S socks-server -P 1080 listen -p unix:/tmp/sshsock 0 remotehost 22&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Like previous but with TLS:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;hose -S socks-server -P 1080 --cert client.crt --key client.key --ca ca.crt listen -p unix:/tmp/sshsock 0 remotehost 22&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;netcat+client&quot; name=&quot;netcat+client&quot;&gt;netcat client&lt;/h3&gt;
&lt;p&gt;It can be used as a netcat client to connect to network ports.&lt;/p&gt;
&lt;p&gt;Connect to remote:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;hose connect remotehost 4583&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Connect to a remote TLS server with certificate based client authentication&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;hose --cert client.crt --key client.key --ca ca.crt connect remotehost 4583&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Connect to a remote host through a SSL socks proxy with client authentication&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;hose -S socks-server -P 1080 --cert client.crt --key client.key --ca ca.crt connect remotehost 4583&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;stunnel&quot; name=&quot;stunnel&quot;&gt;stunnel&lt;/h3&gt;
&lt;p&gt;This is used as a replacement for &lt;code&gt;stunnel&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Accept TLS connections and forward them to a different port.  This is used
to provide TLS encryption to protocol servers that do not support this
out of the box.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;hose --cert server.crt --key server.key listen --unwrap -p localhost 8011 remotehost 11&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Accept unencrypted connection and forward them to a TLS server.  This is used to
give TLS encryption to protocol clients that do not support it out of the box.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;hose --cert server.crt --key server.key listen --wrap -p localhost 11 remotehost 8011&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;SSH+tunnel&quot; name=&quot;SSH+tunnel&quot;&gt;SSH tunnel&lt;/h3&gt;
&lt;p&gt;You can use it as an ssh proxy command to make ssh go through a Socks
proxy:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh -o &quot;ProxyCommand hoses -S sockserver -P 1080 connect %h %p&quot; remotehost&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;References&quot; name=&quot;References&quot;&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.electricmonk.nl/log/2018/06/02/ssl-tls-client-certificate-verification-with-python-v3-4-sslcontext/&quot;&gt;https://www.electricmonk.nl/log/2018/06/02/ssl-tls-client-certificate-verification-with-python-v3-4-sslcontext/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/MisterDaneel/pysoxy&quot;&gt;https://github.com/MisterDaneel/pysoxy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/7186601/is-socks5-bind-persistent-or-one-time-only&quot;&gt;https://stackoverflow.com/questions/7186601/is-socks5-bind-persistent-or-one-time-only&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/SOCKS&quot;&gt;https://en.wikipedia.org/wiki/SOCKS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;socks clients
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://pypi.org/project/PySocks/&quot;&gt;https://pypi.org/project/PySocks/&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;Example &lt;a href=&quot;https://www.positioniseverything.net/py-socks/&quot;&gt;https://www.positioniseverything.net/py-socks/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Using requests:
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/12601316/how-to-make-python-requests-work-via-socks-proxy&quot;&gt;https://stackoverflow.com/questions/12601316/how-to-make-python-requests-work-via-socks-proxy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.worthwebscraping.com/how-to-use-socks-proxy-with-request-module-in-python/&quot;&gt;https://www.worthwebscraping.com/how-to-use-socks-proxy-with-request-module-in-python/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Monkey patching: &lt;a href=&quot;https://www.michaelrinderle.com/2020/05/21/patching-a-python-socket-to-use-socks5-protocol/&quot;&gt;https://www.michaelrinderle.com/2020/05/21/patching-a-python-socket-to-use-socks5-protocol/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>OpenSSH Certificate Authority</title>
<link href="https://www.0ink.net/posts/2024/2024-05-15-openssh-ca.html"></link>
<id>urn:uuid:467bce47-10d8-438b-4516-86dd39b174a7</id>
<updated>2024-03-05T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Intro
Creating CA
Configuring Hosts
Singing user public keys
Using certificates
...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Intro&quot;&gt;Intro&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Creating+CA&quot;&gt;Creating CA&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Configuring+Hosts&quot;&gt;Configuring Hosts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Singing+user+public+keys&quot;&gt;Singing user public keys&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Using+certificates&quot;&gt;Using certificates&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Further+reading&quot;&gt;Further reading&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/OpenSSH_logo.png&quot; alt=&quot;openssh logo&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;Intro&quot; name=&quot;Intro&quot;&gt;Intro&lt;/h2&gt;
&lt;p&gt;OpenSSH supports a &lt;strong&gt;Certificate Authority&lt;/strong&gt; (&lt;strong&gt;CA&lt;/strong&gt;) function.&lt;/p&gt;
&lt;p&gt;This allows you to distribute one or more trusted &lt;strong&gt;CA&lt;/strong&gt; keys.  Then if you
want to give access to users, you simply sign their public keys with
the &lt;strong&gt;CA&lt;/strong&gt; private key.&lt;/p&gt;
&lt;p&gt;Users then can login to a server without having to have their public keys
in the &lt;code&gt;authorized_keys&lt;/code&gt; file as long as their public key was signed
by a trusted &lt;strong&gt;CA&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Furthermore, when signing keys, you can apply further restrictions such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;user allowed to login as&lt;/li&gt;
&lt;li&gt;validity periods&lt;/li&gt;
&lt;li&gt;SSH restrictions similar to the options in the &lt;code&gt;authorized_keys&lt;/code&gt; file. e.g.
forced command, no-pty`, restrict tcp forwarding, etc.&lt;/li&gt;
&lt;li&gt;IP addresses that the key can originate from&lt;/li&gt;
&lt;li&gt;and more...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For larger organizations with lots of users and servers, makes managing
authorizition keys a much simpler process.&lt;/p&gt;
&lt;h2 id=&quot;Creating+CA&quot; name=&quot;Creating+CA&quot;&gt;Creating &lt;strong&gt;CA&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;To begin using OpenSSH certificates you first must generate an ssh key
that will be kept secret and used as the certificate authority in your
environment. This can be done with a command like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh-keygen -f my_ssh_cert_authority&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That command outputs two files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;my_ssh_cert_authority&lt;/code&gt;: The encrypted private key for your new authority&lt;/li&gt;
&lt;li&gt;&lt;code&gt;my_ssh_cert_authority.pub&lt;/code&gt;: The public key for your new authority.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Be sure you choose a passphrase when prompted so that the secret is
stored encrypted. Other options to &lt;code&gt;ssh-keygen&lt;/code&gt; are permitted including
both key type and key parameters. For example, you might choose to use
ECDSA keys instead of RSA.&lt;/p&gt;
&lt;p&gt;Grab the fingerprint of your new CA:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ ssh-keygen -l -f my_ssh_cert_authority
2048 2b:a1:16:84:79:0a:2e:38:84:6f:32:96:ab:d4:af:5d my_ssh_cert_authority.pub (RSA)&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Configuring+Hosts&quot; name=&quot;Configuring+Hosts&quot;&gt;Configuring Hosts&lt;/h2&gt;
&lt;p&gt;Now that you have a certificate authority you&#039;ll need to tell the hosts
in your environment to trust this authority. This is done very similar
to user SSH keys by setting up the &lt;code&gt;authorized_keys&lt;/code&gt; on your hosts (the
expectation is that you&#039;re setting this up at launch time via cloudinit
or perhaps baking the change into an OS image or other form of snapshot).&lt;/p&gt;
&lt;p&gt;You have a choice of putting this &lt;code&gt;authorized_keys&lt;/code&gt; file into
&lt;code&gt;$HOME/.ssh/authorized_keys&lt;/code&gt; or the change can be made system wide. For
system wide configuration see &lt;code&gt;sshd_config(5)&lt;/code&gt; and the
&lt;code&gt;TrustedUserCAKeys&lt;/code&gt; option.&lt;/p&gt;
&lt;p&gt;If you are modifying the user&#039;s &lt;code&gt;authorized_keys&lt;/code&gt; file simply add a new
line to &lt;code&gt;authorized_keys&lt;/code&gt; of the form:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cert-authority &amp;lt;paste the single line from my_ssh_cert_authority.pub&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A valid line might look like this for an RSA key:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cert-authority ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAYQC6Shl5kUuTGqkSc8D2vP2kls2GoB/eGlgIb0BnM/zsIsbw5cWsPournZN2IwnwMhCFLT/56CzT9ZzVfn26hxn86KMpg76NcfP5Gnd66dsXHhiMXnBeS9r6KPQeqzVInwE=&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If configuring system-wide, modify your &lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt; to include:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;TrustedUserCAKeys /etc/ssh/ca_keys&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Copy the &lt;strong&gt;CA&lt;/strong&gt; public keys:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAYQC6Shl5kUuTGqkSc8D2vP2kls2GoB/eGlgIb0BnM/zsIsbw5cWsPournZN2IwnwMhCFLT/56CzT9ZzVfn26hxn86KMpg76NcfP5Gnd66dsXHhiMXnBeS9r6KPQeqzVInwE=&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can have multiple &lt;strong&gt;CA&lt;/strong&gt; public keys in this file.  This can be used
to keep &lt;strong&gt;CA&lt;/strong&gt; keys off-line and rotate them on a regular basis.  Storing
offline &lt;strong&gt;CA&lt;/strong&gt; keys is also useful to provide emergency access in the event
of a compromised &lt;strong&gt;CA&lt;/strong&gt; key.&lt;/p&gt;
&lt;h2 id=&quot;Singing+user+public+keys&quot; name=&quot;Singing+user+public+keys&quot;&gt;Singing user public keys&lt;/h2&gt;
&lt;p&gt;At this point your host has been configured to accept a certificate
signed by your authority&#039;s private key. Let&#039;s generate a certificate for
ourselves that permits us to login as the user ubuntu and that is valid
for the next hour (This assumes that our personal public SSH key is
stored at &lt;code&gt;~/.ssh/id_rsa.pub)&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;ssh-keygen -V +1h -s my_ssh_cert_authority -I bvanzant -n ubuntu ~/.ssh/id_rsa.pub&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output of that command is the file &lt;code&gt;~/.ssh/id_rsa-cert.pub&lt;/code&gt;. If you
open it it&#039;s just a base64 encoded blob. However, we can ask &lt;code&gt;ssh-keygen&lt;/code&gt;
to show us the contents:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;$ ssh-keygen -L -f ~/.ssh/id_rsa-cert.pub
/tmp/test_main_ssh-cert.pub:
    Type: ssh-rsa-cert-v01@openssh.com user certificate
    Public key: RSA-CERT f6:e3:42:5e:72:85:ce:26:e8:45:1f:79:2d:dc:0d:52
    Signing CA: RSA 4c:c6:1e:31:ed:7b:7c:33:ff:7d:51:9e:59:da:68:f5
    Key ID: &quot;bvz-test&quot;
    Serial: 0
    Valid: from 2015-04-13T06:48:00 to 2015-04-13T07:49:13
    Principals:
            ubuntu
    Critical Options: (none)
    Extensions:
            permit-X11-forwarding
            permit-agent-forwarding
            permit-port-forwarding
            permit-pty
            permit-user-rc&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Using+certificates&quot; name=&quot;Using+certificates&quot;&gt;Using certificates&lt;/h2&gt;
&lt;p&gt;Let&#039;s use the certificate now::&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;# Add the key into our ssh-agent (this will find and add the certificate as well)
ssh-add ~/.ssh/id_rsa
# And SSH to a host
ssh ubuntu@&amp;lt;the host where you modified authorized_keys&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the steps above were followed carefully you&#039;re now SSHed to the
remote host. Fancy?&lt;/p&gt;
&lt;p&gt;At this point if you look in &lt;code&gt;/var/log/auth.log&lt;/code&gt; (Ubuntu) (&lt;code&gt;/var/log/secure&lt;/code&gt;
on Red Hat based systems) you&#039;ll see that the user ubuntu logged in to this
machine. This isn&#039;t very useful data. If you change the sshd_config on your
servers to include &lt;code&gt;LogLevel VERBOSE&lt;/code&gt; you&#039;ll see that the certificate key id
is also logged when a user logs in via certificate. This allows you to map
that user &lt;code&gt;bvanzant&lt;/code&gt; logged into the host using username ubuntu. This will
make your auditors happy.&lt;/p&gt;
&lt;h2 id=&quot;Further+reading&quot; name=&quot;Further+reading&quot;&gt;Further reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Revocations: &lt;a href=&quot;https://docs.rundeck.com/docs/learning/howto/revoke-ssh-keys.html&quot;&gt;https://docs.rundeck.com/docs/learning/howto/revoke-ssh-keys.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Server based &lt;strong&gt;CA&lt;/strong&gt;: &lt;a href=&quot;https://github.com/cloudtools/ssh-cert-authority&quot;&gt;https://github.com/cloudtools/ssh-cert-authority&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;An implementation in Python as a AWS Lambda: &lt;a href=&quot;https://github.com/Netflix/bless&quot;&gt;https://github.com/Netflix/bless&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Why Ansible?</title>
<link href="https://www.0ink.net/posts/2024/2024-05-01-ansible.html"></link>
<id>urn:uuid:5096f7f1-cb45-7773-ff8e-22706494f39f</id>
<updated>2024-03-05T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
As part of an effort of standardising my home lab I decided to migrate my
ad-hoc configuration scripts into a more standard tool set.  So I looked at:

puppet
chef
...]]></summary>
<content type="html">&lt;p&gt;&lt;img src=&quot;/images/2024/ansible_logo.png&quot; alt=&quot;ansible logo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;As part of an effort of standardising my home lab I decided to migrate my
ad-hoc configuration scripts into a more standard tool set.  So I looked at:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.puppet.com/&quot;&gt;puppet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.chef.io/&quot;&gt;chef&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://saltproject.io/&quot;&gt;saltstack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.ansible.com/&quot;&gt;ansible&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.cdi.st/&quot;&gt;cdist&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cfengine.com/&quot;&gt;cfengine&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At the end I opted for &lt;a href=&quot;https://www.ansible.com/&quot;&gt;ansible&lt;/a&gt;, as it was closer in operation to my
configuration scripts.  The main points for me where:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Central control node, with push semantics&lt;/li&gt;
&lt;li&gt;No agent needed, all communications via &lt;code&gt;ssh&lt;/code&gt;.  But it has &lt;code&gt;python&lt;/code&gt; dependancies.&lt;/li&gt;
&lt;li&gt;Written in &lt;code&gt;python&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Configuration mostly in YAML files.&lt;/li&gt;
&lt;li&gt;Very wide acceptance in the IT community.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Later I found about &lt;a href=&quot;https://www.cdi.st/&quot;&gt;cdist&lt;/a&gt;, while it is closer to my ad-hoc scripts, it is
a bit niche and not well known.  However, like &lt;a href=&quot;https://www.ansible.com/&quot;&gt;ansible&lt;/a&gt; has a central
control node push model, all communications via &lt;code&gt;ssh&lt;/code&gt; and has no &lt;code&gt;python&lt;/code&gt;
dependancies, requiring only a compatible &lt;code&gt;shell&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Configuration of &lt;a href=&quot;https://www.cdi.st/&quot;&gt;cdist&lt;/a&gt; is written in essentially &lt;code&gt;shell&lt;/code&gt; scripts.  According
to &lt;a href=&quot;https://en.wikipedia.org/wiki/Cdist#Similar_software&quot;&gt;this wikipedia article&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Ansible makes a distinction between roles, written in a declarative YAML-based
language, and modules, written in Python. Cdist only has &amp;quot;types&amp;quot; which serve the
purposes of both modules and roles and are mostly written in Bourne Shell. Cdist&#039;s
approach might be preferable because Shell is familiar to many system
administrators who have never used a configuration management system before,
but Ansible&#039;s declarative language is arguably more readable and appropriate.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The remaining options all require agents, which is not ideal as I really wanted
a low footprint on the managed nodes.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cfengine.com/&quot;&gt;cfengine&lt;/a&gt; has a nice theoretical framework but also, not as popular as
&lt;a href=&quot;https://www.ansible.com/&quot;&gt;ansible&lt;/a&gt; or the other entries in this list.  While written in &lt;code&gt;C&lt;/code&gt; it
uses a &lt;code&gt;DSL&lt;/code&gt; for its configurations.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.puppet.com/&quot;&gt;puppet&lt;/a&gt; is written in a mix of of Ruby, Clojure and C++ and uses a DSL for
configuration.  &lt;a href=&quot;https://www.chef.io/&quot;&gt;chef&lt;/a&gt; is written in a mix of Ruby and ERLAN and uses DSL
for Configuration.  Personally, I see the use of Ruby as a turn-off.  So far
several softwares I tried to use written in Ruby did not lead to good experiences
for me.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://saltproject.io/&quot;&gt;saltstack&lt;/a&gt; is written in &lt;code&gt;python&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;Removing+Python+as+a+dependancy&quot; name=&quot;Removing+Python+as+a+dependancy&quot;&gt;Removing Python as a dependancy&lt;/h2&gt;
&lt;p&gt;I am mostly using &lt;a href=&quot;https://alpinelinux.org/&quot;&gt;Alpine Linux&lt;/a&gt; for servers.  As such, adding &lt;code&gt;python&lt;/code&gt; adds
to a base image of 17.1MB, an additional 50MB.  So it is a bit unreasonable.  I found
a project &lt;a href=&quot;https://github.com/gekmihesg/ansible-openwrt&quot;&gt;ansible-openwrt&lt;/a&gt; which
removes the &lt;code&gt;python&lt;/code&gt; dependancies but it has a lot of &lt;a href=&quot;https://openwrt.org/&quot;&gt;OpenWRT&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I manage remove the OpenWRT dependancies, and the result can be found &lt;a href=&quot;https://github.com/TortugaLabs/ansible-nopython&quot;&gt;HERE&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It makes use of the Ansible&#039;s &lt;a href=&quot;https://docs.ansible.com/ansible/latest/plugins/vars.html&quot;&gt;var_plugin&lt;/a&gt;
functionality to hook into the &lt;code&gt;ActionBase._configure_module&lt;/code&gt; method.&lt;/p&gt;
&lt;p&gt;Also adds a number of modules written in &lt;code&gt;shell&lt;/code&gt; script that replace the built-in python
modules.  Specially important are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;setup: which is called automatically to gather facts at start.&lt;/li&gt;
&lt;li&gt;ping: because, it is the first thing to test things.&lt;/li&gt;
&lt;li&gt;stat: which is used by several modules specially copy.&lt;/li&gt;
&lt;li&gt;command: which is used by command and shell modules.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Interestingly enough, there are other modules such as &lt;code&gt;script&lt;/code&gt; that do not have a python dependency.&lt;/p&gt;</content>
</entry>
<entry>
<title>OpenTofu</title>
<link href="https://www.0ink.net/posts/2024/2024-04-15-opentofu.html"></link>
<id>urn:uuid:5480bb6e-4791-ba8c-a314-00f4c9e8863f</id>
<updated>2024-11-22T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Introduction
Origins
Why use OpenTofu
Using OpenTofu

...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Introduction&quot;&gt;Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Origins&quot;&gt;Origins&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Why+use+OpenTofu&quot;&gt;Why use OpenTofu&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Using+OpenTofu&quot;&gt;Using OpenTofu&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Installation&quot;&gt;Installation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Configuring+for+OCI&quot;&gt;Configuring for OCI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Setting+up+API+Key-Based+authentication&quot;&gt;Setting up API Key-Based authentication&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Declare+networking&quot;&gt;Declare networking&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Declare+compute+instance&quot;&gt;Declare compute instance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Run+Scripts&quot;&gt;Run Scripts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Destroying+Infrastructure&quot;&gt;Destroying Infrastructure&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;/images/2024/opentofu.png&quot; alt=&quot;opentofu logo&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;Introduction&quot; name=&quot;Introduction&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://opentofu.org/&quot;&gt;OpenTofu&lt;/a&gt; is open-source fork of &lt;a href=&quot;https://www.terraform.io/&quot;&gt;Terraform&lt;/a&gt; from &lt;a href=&quot;https://www.hashicorp.com/&quot;&gt;HashiCorp&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://opentofu.org/&quot;&gt;OpenTofu&lt;/a&gt; is an &amp;quot;Infrastructure-as-code&amp;quot; software tool.  Users can use it
to  define and provide data center infrastructure using a declarative configuration
language known as HashiCorp Configuration Language (HCL), or optionally JSON.&lt;/p&gt;
&lt;p&gt;I first started to explore &lt;a href=&quot;https://opentofu.org/&quot;&gt;OpenTofu&lt;/a&gt; because I wanted to migrate some hosted
VMs from the &lt;a href=&quot;https://www.open-telekom-cloud.com/nl&quot;&gt;OpenTelekomCloud&lt;/a&gt;.  Back then, I was using a custom script
to deploy infrastructure to the cloud from description files written in YAML.&lt;/p&gt;
&lt;p&gt;I found the approach of deploying cloud infrastructure from description files
much more convenient to doing the same from the Web UI.&lt;/p&gt;
&lt;p&gt;While the Web UI is very useful to explore the capabilities of a cloud service,
for a more &amp;quot;production-like&amp;quot; infrastructure, I would consider that approach
very poor.  Deploying several VMs, would require multiple clicks, depending
on good memory skills to make sure that deployment were consistent.&lt;/p&gt;
&lt;p&gt;When I was contemplating migrating this VMs from &lt;a href=&quot;https://www.open-telekom-cloud.com/nl&quot;&gt;OTC&lt;/a&gt; to &lt;a href=&quot;https://www.oracle.com/cloud/&quot;&gt;Oracle Cloud&lt;/a&gt;
I felt doing it via the Web UI very unapealing.  And while there is an
API available, I did not find the inclination to write a custom script.&lt;/p&gt;
&lt;p&gt;Recently, speaking to some co-workers, I was reminded of &lt;a href=&quot;https://www.terraform.io/&quot;&gt;Terraform&lt;/a&gt;.
And decided to try that this time.  Of course, at the time, &lt;a href=&quot;https://www.hashicorp.com/&quot;&gt;HashiCorp&lt;/a&gt;
had recently switched licenses, so switched to &lt;a href=&quot;https://opentofu.org/&quot;&gt;OpenTofu&lt;/a&gt; instead.&lt;/p&gt;
&lt;p&gt;My expectation is that migrating between &lt;a href=&quot;https://opentofu.org/&quot;&gt;OpenTofu&lt;/a&gt; and &lt;a href=&quot;https://www.terraform.io/&quot;&gt;Terraform&lt;/a&gt;
and back should be a simple experience, and any learned skills in
&lt;a href=&quot;https://opentofu.org/&quot;&gt;OpenTofu&lt;/a&gt; should translate directly into &lt;a href=&quot;https://www.terraform.io/&quot;&gt;Terraform&lt;/a&gt;.  In fact,
except for the installation of &lt;a href=&quot;https://opentofu.org/&quot;&gt;OpenTofu&lt;/a&gt;, I have been following tutorials
for &lt;a href=&quot;https://www.terraform.io/&quot;&gt;Terraform&lt;/a&gt; instead.  So far everything has been essentially the same.&lt;/p&gt;
&lt;h2 id=&quot;Origins&quot; name=&quot;Origins&quot;&gt;Origins&lt;/h2&gt;
&lt;p&gt;In August 2023 &lt;a href=&quot;https://www.terraform.io/&quot;&gt;Terraform&lt;/a&gt; (along with several of &lt;a href=&quot;https://www.hashicorp.com/&quot;&gt;HashiCorp&lt;/a&gt;&#039;s products)
switched from an Open Source license (MPL2.0) to non Open-Source
&lt;a href=&quot;https://github.com/hashicorp/terraform/blob/main/LICENSE&quot;&gt;Business Source License&lt;/a&gt;.  This change prompted a group of users
to fork the last available version of &lt;a href=&quot;https://www.terraform.io/&quot;&gt;Terraform&lt;/a&gt;, v1.5.5, as &lt;a href=&quot;https://opentofu.org/&quot;&gt;OpenTofu&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;Why+use+OpenTofu&quot; name=&quot;Why+use+OpenTofu&quot;&gt;Why use OpenTofu&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://opentofu.org/&quot;&gt;OpenTofu&lt;/a&gt; is an &amp;quot;Infrastructure-as-code&amp;quot; software, this allows you to
build, change, and manage your infrastructure in a safe, consistent, trackable,
and repeatable way by defining resource configurations that you can version (in
a version control system like GitHub), reuse, and share.&lt;/p&gt;
&lt;p&gt;This yields to more reliable infrastructure, and at the same time allow you
to quickly make/implement changes to it in response of changing requirements.&lt;/p&gt;
&lt;h2 id=&quot;Using+OpenTofu&quot; name=&quot;Using+OpenTofu&quot;&gt;Using OpenTofu&lt;/h2&gt;
&lt;h3 id=&quot;Installation&quot; name=&quot;Installation&quot;&gt;Installation&lt;/h3&gt;
&lt;p&gt;The official installation instructions can be found in their
&lt;a href=&quot;https://opentofu.org/docs/intro/install/&quot;&gt;Documentation Webpage&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Whenever possible using your native package manager (e.g. apt on Debian Linux,
dnf on Fedora, etc) would be the preferred option.  In my case, I am using
Void Linux, so that option was not available to me.&lt;/p&gt;
&lt;p&gt;Luckly, &lt;a href=&quot;https://opentofu.org/&quot;&gt;OpenTofu&lt;/a&gt; is implemented as a single binary executable (It is
developed in go language), so simply downloading this from their
&lt;a href=&quot;https://github.com/opentofu/opentofu/releases/latest&quot;&gt;release page&lt;/a&gt;
and adding it somewhere in your executable path to be sufficient.&lt;/p&gt;
&lt;p&gt;There even is a Windows version which can be installed in a similar way.&lt;/p&gt;
&lt;p&gt;For your convenience they provide with an &amp;quot;installation&amp;quot; script, but
being it a single binary, I felt that this was irrelevant.&lt;/p&gt;
&lt;h3 id=&quot;Configuring+for+OCI&quot; name=&quot;Configuring+for+OCI&quot;&gt;Configuring for OCI&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Sign up for &lt;a href=&quot;https://docs.oracle.com/iaas/Content/GSG/Tasks/signingup.htm&quot;&gt;Oracle Cloud&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;You can skip these steps if using an &lt;strong&gt;Administrator&lt;/strong&gt; user.  However, it is
highly recommended to use a &lt;em&gt;&amp;quot;non-Administrator&amp;quot;&lt;/em&gt; account when available.
&lt;ul&gt;
&lt;li&gt;Create Compartment:
&lt;ul&gt;
&lt;li&gt;Go to &lt;strong&gt;Identity&lt;/strong&gt;, &lt;strong&gt;Compartments&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create Compartment&lt;/strong&gt;.  Complete the form and create the compartment.&lt;/li&gt;
&lt;li&gt;Wait a couple of minutes for the compartment to be create.&lt;/li&gt;
&lt;li&gt;Note and record the compartment name and ID (which will be needed later)&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Create Group:
&lt;ul&gt;
&lt;li&gt;Go to &lt;strong&gt;Identity&lt;/strong&gt;, &lt;strong&gt;Domains&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Select the desired domain.  Either create a new domain or use the &lt;strong&gt;default&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;On the sidebar, click on &lt;strong&gt;Groups&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click on &lt;strong&gt;Create group&lt;/strong&gt; and complete the form.  You can leave users empty
and create the API user later.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Create a Policy:
&lt;ul&gt;
&lt;li&gt;Go to &lt;strong&gt;Identity&lt;/strong&gt;, &lt;strong&gt;Policies&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create policy&lt;/strong&gt;. Enter &lt;strong&gt;Name&lt;/strong&gt; and &lt;strong&gt;Description&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Note: The form can select a &lt;strong&gt;Compartment&lt;/strong&gt;.  I don&#039;t know how this works
as I only tested on the &lt;em&gt;root&lt;/em&gt; compartment.&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Show manual editor&lt;/strong&gt; and enter this as the policy:
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;allow group &amp;lt;group-name&amp;gt; to read all-resources in tenancy
allow group &amp;lt;group-name&amp;gt; to manage all-resources in compartment &amp;lt;compartment&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Create API user:
&lt;ul&gt;
&lt;li&gt;Go to &lt;strong&gt;Identity&lt;/strong&gt;, &lt;strong&gt;Domains&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Select the desired domain.  Either create a new domain or use the &lt;strong&gt;default&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;On the sidebar, click on &lt;strong&gt;Users&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click on &lt;strong&gt;Create user&lt;/strong&gt; and complete the form.  Add the user to the relevant
group.&lt;br /&gt;
This way the scope of access for a given user/group is limited to a compartment.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Create RSA keys, this can be done either via command line or using the Web Console.
&lt;ul&gt;
&lt;li&gt;From the command line:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;openssl genrsa -out &amp;lt;your-home-directory&amp;gt;/.oci/&amp;lt;your-rsa-key-name&amp;gt;.pem 2048&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chmod 600 &amp;lt;your-home-directory&amp;gt;/.oci/&amp;lt;your-rsa-key-name&amp;gt;.pem&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;openssl rsa -pubout -in &amp;lt;your-home-directory&amp;gt;/.oci/&amp;lt;your-rsa-key-name&amp;gt;.pem -out $HOME/.oci/&amp;lt;your-rsa-key-name&amp;gt;_public.pem&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Assign key to user account:
&lt;ul&gt;
&lt;li&gt;Go to &lt;strong&gt;Identity&lt;/strong&gt;, &lt;strong&gt;Domains&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Select the desired domain.  Either create a new domain or use the &lt;strong&gt;default&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;On the sidebar, click on &lt;strong&gt;Users&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Select the API user.&lt;/li&gt;
&lt;li&gt;On the left sidebar click on &lt;strong&gt;API Keys&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Add API Key&lt;/strong&gt;.  Select &lt;strong&gt;Paste Public Keys&lt;/strong&gt; and paste the value of the
contents of the public key file.  Click &lt;strong&gt;Add&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;From the Web Console:
&lt;ul&gt;
&lt;li&gt;Go to &lt;strong&gt;Identity&lt;/strong&gt;, &lt;strong&gt;Domains&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Select the desired domain.  Either create a new domain or use the &lt;strong&gt;default&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;On the sidebar, click on &lt;strong&gt;Users&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Select the API user.&lt;/li&gt;
&lt;li&gt;On the left sidebar click on &lt;strong&gt;API Keys&lt;/strong&gt;. Select &lt;strong&gt;Generate API Key pair&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Download &lt;strong&gt;Private key&lt;/strong&gt;.  You may download the public key, but that is strictly optional.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Add&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;After adding the key, save the sample &lt;code&gt;config&lt;/code&gt; information.  You will need it later.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;Setting+up+API+Key-Based+authentication&quot; name=&quot;Setting+up+API+Key-Based+authentication&quot;&gt;Setting up API Key-Based authentication&lt;/h3&gt;
&lt;p&gt;Create a working directory to be used in our &lt;a href=&quot;https://opentofu.org/&quot;&gt;OpenTofu&lt;/a&gt; project.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a file named &lt;code&gt;provider.tf&lt;/code&gt; with the following contents:
&lt;pre&gt;&lt;code&gt;provider &quot;oci&quot; {
 tenancy_ocid = &quot;&amp;lt;tenancy-ocid&amp;gt;&quot;
 user_ocid = &quot;&amp;lt;user-ocid&quot;
 fingerprint = &quot;&amp;lt;fingerprint&amp;gt;&quot;
 region = var.region_name
 private_key_path = &quot;&amp;lt;private-key-path&amp;gt;&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;tenancy_ocid&lt;/code&gt;, &lt;code&gt;user_ocid&lt;/code&gt; and &lt;code&gt;fingerprint&lt;/code&gt; would have come from the &lt;code&gt;config&lt;/code&gt;
settings in the last step of &amp;quot;Configuring OCI&amp;quot;.  The region name is used in more
locations so we will record it as variable so we don&#039;t specify it here. &lt;br /&gt;The &lt;em&gt;private-key-path&lt;/em&gt; is the path to the private RSA key from an earlier step.
Use Linux/UNIX style &amp;quot;slash&amp;quot; (&lt;code&gt;/&lt;/code&gt;) directory separators here even under MS-Windows.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;Create a &lt;code&gt;vars.tf&lt;/code&gt; with contents:
&lt;pre&gt;&lt;code&gt;#
# Define variables
#
variable &quot;compartment_ocid&quot; {
  type        = string
  description = &quot;ID of the compartment in OCI to use&quot;
  default     = &quot;&amp;lt;compartment-ocid&amp;gt;&quot;
}
variable &quot;region_name&quot; {
  type        = string
  description = &quot;Region to deploy resources&quot;
  default     = &quot;&amp;lt;region&amp;gt;&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;em&gt;compartment-ocid&lt;/em&gt; comes from the compartment that was created during
&amp;quot;Configuring OCI&amp;quot;. &lt;br /&gt;The &lt;em&gt;region&lt;/em&gt; comes from the sample &lt;code&gt;config&lt;/code&gt; from &amp;quot;Configuring OCI&amp;quot;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;Create a file &lt;code&gt;availability-domains.tf&lt;/code&gt; with contents:
&lt;pre&gt;&lt;code&gt;data &quot;oci_identity_availability_domains&quot; &quot;tutorial_ads&quot; {
 compartment_id = var.compartment_ocid
}&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;Declare+networking&quot; name=&quot;Declare+networking&quot;&gt;Declare networking&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a file &lt;code&gt;vcn-module.tf&lt;/code&gt; with contents:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module &quot;vcn&quot; {
  source  = &quot;oracle-terraform-modules/vcn/oci&quot;
  version = &quot;3.1.0&quot;
  #
  # Required Inputs
  compartment_id = var.compartment_ocid
  region = var.region_name
  internet_gateway_route_rules = null
  local_peering_gateways = null
  nat_gateway_route_rules = null
  #
  # Optional Inputs
  vcn_name = &quot;&amp;lt;vcn-name&amp;gt;&quot;
  vcn_dns_label = &quot;&amp;lt;vcn-dns-label&amp;gt;&quot;
  vcn_cidrs = [&quot;10.0.0.0/16&quot;]
  #

  create_internet_gateway = true
  create_nat_gateway = false
  create_service_gateway = false
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; In this example, we are fixing the module version.  Don&#039;t know
if this is needed or not.&lt;br /&gt;Select a suitable name for virtual cloud&#039;s &lt;em&gt;vcn-name&lt;/em&gt;. &lt;br /&gt;Similarly, select a suitable &lt;em&gt;vcn-dns-label&lt;/em&gt;.  However, in Oracle Free tiers
there are no DNS zones, so this setting is useless.&lt;br /&gt;&lt;code&gt;vcn_cidrs&lt;/code&gt; in this example is set to &lt;code&gt;10.0.0.0/16&lt;/code&gt;.  Feel free to modify
as needed.&lt;br /&gt;We are only setting &lt;code&gt;create_internet_gateway&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt;.  This allows VMs
in the public subnets to be reachable from the Internet.  This is the
only gateway allowed in the Oracle Free tier.&lt;br /&gt;&lt;code&gt;create_nat_gateway&lt;/code&gt; is set to &lt;code&gt;false&lt;/code&gt; as this is not supported by the
Oracle Free tier.  This gateway allows VMs in the private subnets to
communicate to the Internet.  There is no in-bound access from the Internet
to the private subnets.&lt;br /&gt;&lt;code&gt;create_service_gateway&lt;/code&gt; is set to &lt;code&gt;false&lt;/code&gt; as this is not supported by the
Oracle Free tier.  This gateway allows VMs to communicate to the Oracle
service end-points without going over the Internet.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;create a file &lt;code&gt;pub-seclst.tf&lt;/code&gt; with contents:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; resource &quot;oci_core_security_list&quot; &quot;public-security-list&quot; {

   # Required
   compartment_id = var.compartment_ocid
   vcn_id = module.vcn.vcn_id

   # Optional
   display_name = &quot;security-list-for-public-subnet&quot;

   ingress_security_rules { 
       stateless = false
       source = &quot;0.0.0.0/0&quot;
       source_type = &quot;CIDR_BLOCK&quot;
       # Get protocol numbers from https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml TCP is 6
       protocol = &quot;6&quot;
       tcp_options { 
           min = 22
           max = 22
       }
     }
   ingress_security_rules { 
       stateless = false
       source = &quot;0.0.0.0/0&quot;
       source_type = &quot;CIDR_BLOCK&quot;
       # Get protocol numbers from https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml ICMP is 1  
       protocol = &quot;1&quot;

       # For ICMP type and code see: https://www.iana.org/assignments/icmp-parameters/icmp-parameters.xhtml
       icmp_options {
         type = 3
         code = 4
       } 
     }   

   ingress_security_rules { 
       stateless = false
       source = &quot;10.0.0.0/16&quot;
       source_type = &quot;CIDR_BLOCK&quot;
       # Get protocol numbers from https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml ICMP is 1  
       protocol = &quot;1&quot;

       # For ICMP type and code see: https://www.iana.org/assignments/icmp-parameters/icmp-parameters.xhtml
       icmp_options {
         type = 3
       } 
     }

   egress_security_rules {
       stateless = false
       destination = &quot;0.0.0.0/0&quot;
       destination_type = &quot;CIDR_BLOCK&quot;
       protocol = &quot;all&quot; 
   }

 }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This defines the security list protecting the public subnet.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create &lt;code&gt;pub-subnet.tf&lt;/code&gt; with contents:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; resource &quot;oci_core_subnet&quot; &quot;vcn-public-subnet&quot;{

   # Required
   compartment_id = var.compartment_ocid
   vcn_id = module.vcn.vcn_id
   cidr_block = &quot;10.0.255.0/24&quot;

   # Optional
   route_table_id = module.vcn.ig_route_id
   security_list_ids = [oci_core_security_list.public-security-list.id]
   display_name = &quot;public-subnet&quot;
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Change &lt;code&gt;cidr_block&lt;/code&gt;, &lt;code&gt;display_name&lt;/code&gt; as needed.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;Declare+compute+instance&quot; name=&quot;Declare+compute+instance&quot;&gt;Declare compute instance&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Create login keys:
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;ssh-keygen -t rsa -N &quot;&quot; -b 2048 -C &amp;lt;your-ssh-key-name&amp;gt; -f &amp;lt;your-ssh-key-name&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The command generates some random text art used to generate the keys. When complete,
you have two files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The private key file: &lt;em&gt;your-ssh-key-name&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;The public key file: &lt;em&gt;your-ssh-key-name&lt;/em&gt;.pub&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Create &lt;code&gt;cloud-init.yaml&lt;/code&gt; with contents:
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;#cloud-config
runcmd:
- echo &#039;Hello world&#039; &amp;gt;&amp;gt; /etc/motd&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a quick example to demonstrate that it works.  See
&lt;a href=&quot;https://cloudinit.readthedocs.io/en/latest/reference/examples.html&quot;&gt;cloud-init&lt;/a&gt; for more examples.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create &lt;code&gt;compute.tf&lt;/code&gt; with contents:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; resource &quot;oci_core_instance&quot; &quot;&amp;lt;vm-name&amp;gt;&quot; {
     # Required
     availability_domain = data.oci_identity_availability_domains.tutorial_ads.availability_domains[0].name
     compartment_id = var.compartment_ocid
     shape = &quot;VM.Standard.A1.Flex&quot;
     shape_config {
         memory_in_gbs = 2
         ocpus = 1
     }
     source_details {
         source_id = &quot;ocid1.image.oc1.eu-amsterdam-1.aaaaaaaa7o2ilw6qsabd7qgnfjxncygvy442pzxkzcmsogkxeqhtwsgwlnwq&quot;
         source_type = &quot;image&quot;
     }
     #~ shape = &quot;VM.Standard.E2.1.Micro&quot;
     #~ source_details {
         #~ source_id = &quot;ocid1.image.oc1.eu-amsterdam-1.aaaaaaaa57ipifc7jj3m7nskxw66czipcrf4hpehsbx473uauvxot2im67dq&quot;
         #~ source_type = &quot;image&quot;
     #~ }

     # Optional
     display_name = &quot;&amp;lt;vm-name&amp;gt;&quot;
     create_vnic_details {
         assign_public_ip = true
         subnet_id = oci_core_subnet.vcn-public-subnet.id
     }
     metadata = {
         ssh_authorized_keys = file(&quot;&amp;lt;you-sh-key-name&amp;gt;,pub&quot;)
         user_data = &quot;${base64encode(file(&quot;cloud-init.yaml&quot;))}&quot;

     } 
     preserve_boot_volume = false
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;availability_domain&lt;/code&gt; is configured from &lt;code&gt;availability-domains.tf&lt;/code&gt; which retrieves
the list of availability domains and selects the first one.&lt;/li&gt;
&lt;li&gt;In this example, we are setting &lt;em&gt;vm-name&lt;/em&gt; as &lt;em&gt;resource-id&lt;/em&gt; and also as &lt;code&gt;display_name&lt;/code&gt;.
This is not necessary, but it makes things simpler this way.  The &lt;em&gt;resource-id&lt;/em&gt; is
used internally by &lt;a href=&quot;https://opentofu.org/&quot;&gt;OpenTofu&lt;/a&gt; to refer created resources, while the
&lt;code&gt;display_name&lt;/code&gt; is shown in the Oracle web console.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;source_id&lt;/code&gt; can be looked up from the
&lt;a href=&quot;https://docs.oracle.com/en-us/iaas/images/&quot;&gt;documentation&lt;/a&gt;.
Simply find the image you want, and locate the region to use, and get the
ID from there.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;shape&lt;/code&gt; and &lt;code&gt;shape_config&lt;/code&gt; are used to configure the VM, in the Oracle
Free tier, you can use: &lt;code&gt;VM.Standard.A1.Flex&lt;/code&gt; or &lt;code&gt;VM.Standard.E2.1.Micro&lt;/code&gt;.&lt;br /&gt;The &lt;code&gt;Flex&lt;/code&gt; shape requires further configuration with &lt;code&gt;shape_config&lt;/code&gt; which
requires &lt;code&gt;memory_in_gbs&lt;/code&gt; to configure memory and &lt;code&gt;ocpus&lt;/code&gt; to configure
CPU count.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;metadata.ssh_authorized_keys&lt;/code&gt; : Configure authorized ssh keys.  The default
user for the Oracle Ubuntu images is &lt;code&gt;ubuntu&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;metadata.user_data&lt;/code&gt; : User data for initialization.  This must be a
&lt;strong&gt;base64 encoded&lt;/strong&gt; script.  &lt;strong&gt;NOTE&lt;/strong&gt; that because it is &lt;strong&gt;base64 encoded&lt;/strong&gt;
under Microsoft Windows, scripts may not be recognized properly as
including a file (as in the example) will contain Windows style line
terminations which will make the &lt;code&gt;#cloud-config&lt;/code&gt; test fail.  The following
cloud-init formats are supported:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;#cloud-config&lt;/code&gt; Cloud Config Data&lt;/li&gt;
&lt;li&gt;&lt;code&gt;#!&lt;/code&gt; User-Data Script (e.g. &lt;code&gt;#!/bin/bash&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;#include&lt;/code&gt; Include file&lt;/li&gt;
&lt;li&gt;&lt;code&gt;#cloud-boothook&lt;/code&gt; Cloud boothook&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;Run+Scripts&quot; name=&quot;Run+Scripts&quot;&gt;Run Scripts&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Initialize
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;tf init&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This initializes a working directory.  Specifically it would download
from the Terraform repository any providers and/or modules.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;Create an &lt;strong&gt;execution plan&lt;/strong&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;tf plan&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;use this to preview what will &lt;a href=&quot;https://opentofu.org/&quot;&gt;OpenTofu&lt;/a&gt; will eventually execute.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;Run your terraform scripts:
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;tf apply&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will execute changes to the infrastructure.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;Destroying+Infrastructure&quot; name=&quot;Destroying+Infrastructure&quot;&gt;Destroying Infrastructure&lt;/h3&gt;
&lt;p&gt;Run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt; tf destroy&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will destroy any created resources.&lt;/p&gt;
&lt;p&gt;The given example creates a ARM server.  At the time of this writing, the
Oracle Free tier does not have any Arm servers available.  Running these
scripts will generate error:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Error: 500-InternalError, Out of host capacity.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unfortunately, there is not much that can be done here.  You try running things
later hopping that some capacity may be freed-up.&lt;/p&gt;</content>
</entry>
<entry>
<title>Inetd like service with systemd</title>
<link href="https://www.0ink.net/posts/2024/2024-04-01-systemd-socket.html"></link>
<id>urn:uuid:0c709b9a-d07c-57b3-9a23-7838a24bf810</id>
<updated>2024-11-22T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
This is an example of a socket-activated per-connection service (which is usually referred to
as inetd-like service).
A thorough explanation can be found at 0pointer.de.
Define a socket unit
The key point here is to specify Accept=yes, which will make the socket accept connections (behaving like inetd) and pass
...]]></summary>
<content type="html">&lt;p&gt;&lt;img src=&quot;/images/2024/sysd0.png&quot; alt=&quot;sysd0&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This is an example of a socket-activated per-connection service (which is usually referred to
as inetd-like service).&lt;/p&gt;
&lt;p&gt;A thorough explanation can be found at &lt;a href=&quot;http://0pointer.de/blog/projects/inetd.html&quot;&gt;0pointer.de&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;Define+a+socket+unit&quot; name=&quot;Define+a+socket+unit&quot;&gt;Define a socket unit&lt;/h2&gt;
&lt;p&gt;The key point here is to specify &lt;code&gt;Accept=yes&lt;/code&gt;, which will make the socket accept connections (behaving like inetd) and pass
only the resulting connection socket to the service handler.&lt;/p&gt;
&lt;p&gt;Create &lt;code&gt;/etc/systemd/system/baz.socket&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;[Unit]
Description=Baz Socket

[Socket]
ListenStream=127.0.0.1:9999
Accept=yes

[Install]
WantedBy=sockets.target&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Define+a+service+template+unit&quot; name=&quot;Define+a+service+template+unit&quot;&gt;Define a service template unit&lt;/h2&gt;
&lt;p&gt;We now create a service template from which the actual service will be instantiated on-demand.
The lifetime of this service is usually very short (thus it may not appear at &lt;code&gt;systemctl&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Create &lt;code&gt;/etc/systemd/system/baz@.service&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;[Unit]
Description=Baz Service
Requires=baz.socket

[Service]
Type=simple
ExecStart=/usr/bin/python /opt/baz-service/serve.py %i
StandardInput=socket
StandardError=journal
TimeoutStopSec=5
#RuntimeMaxSec=10

[Install]
WantedBy=multi-user.target&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;An example service handler would be (located at &lt;code&gt;/opt/baz-service/serve.py&lt;/code&gt; as specified at the
service unit file):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;#!/usr/bin/python
import sys
import logging
logging.basicConfig(level=logging.INFO)

instance = sys.argv[1]

# The connected socket is duplicated to stdin/stdout
data = sys.stdin.readline().strip()
logging.info(&#039;baz-service: at instance %s, got request: %s&#039;, instance, data)
sys.stdout.write(data.upper() + &#039;\r\n&#039;)&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Test&quot; name=&quot;Test&quot;&gt;Test&lt;/h2&gt;
&lt;p&gt;Start (or enable it to start at boot time) the socket (verify it via &lt;code&gt;systemctl status baz.socket&lt;/code&gt;),
and make a request to the service:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;echo Hello| nc localhost 9999&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You &#039;ll notice that the socket&#039;s status has changed (i.e. &lt;code&gt;Accepted: 1&lt;/code&gt;). Also, any log
message generated by the on-demand service should be present at the journal (e.g.
&lt;code&gt;journalctl --all| grep -e baz&lt;/code&gt;). &lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/drmalex07/28de61c95b8ba7e5017c&quot;&gt;https://gist.github.com/drmalex07/28de61c95b8ba7e5017c&lt;/a&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>Locking down SFTP</title>
<link href="https://www.0ink.net/posts/2024/2024-03-15-sftp.html"></link>
<id>urn:uuid:bc01ad51-fca0-7429-3ced-79fd793cb8c6</id>
<updated>2024-11-22T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
This is a small recipe to increase the security around a SFTP interface.
In the /etc/ssh/sshd_config file include the following settings:
Subsystem sftp internal-sftp
This configures the sftp subsystem to use the internal sftp implementation.
This is because inside the chroot, we usually will not have the normal
...]]></summary>
<content type="html">&lt;p&gt;&lt;img src=&quot;/images/2024/sftp.png&quot; alt=&quot;sftp&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This is a small recipe to increase the security around a SFTP interface.&lt;/p&gt;
&lt;p&gt;In the &lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt; file include the following settings:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Subsystem sftp internal-sftp&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This configures the sftp subsystem to use the internal sftp implementation.
This is because inside the chroot, we usually will not have the normal
&lt;code&gt;sftp-server&lt;/code&gt; executable.&lt;/p&gt;
&lt;p&gt;For each user that will be doing &lt;code&gt;sftp&lt;/code&gt; do:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Match User sftp-only-user-name
  ChrootDirectory /only/path
  ForceCommand internal-sftp
  X11Forwarding no
  AllowTcpForwarding no
  PermitTTY no&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alternative you could do &lt;code&gt;Match Group&lt;/code&gt; and have multiple sftp-only users in the
specified group.&lt;/p&gt;
&lt;p&gt;The options are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ChrootDirectory /only/path&lt;/code&gt; : Note that this directory must have mode &lt;code&gt;0755&lt;/code&gt; and be
owned by root.  If this is not the case, logins will fail with error: &lt;br /&gt;&lt;code&gt;bad ownership or modes for chroot directory&lt;/code&gt; \&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ForceCommand internal-sftp&lt;/code&gt; : Only allow &lt;code&gt;sftp&lt;/code&gt;.  No other command will be allowed.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;X11Forwarding&lt;/code&gt;, &lt;code&gt;AllowTcpForwarding&lt;/code&gt;, &lt;code&gt;PermitTTY&lt;/code&gt; as &lt;code&gt;no&lt;/code&gt; : These make sure that
the remote user doesn&#039;t try to open holes at the SSH protocol levels.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;References:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.baeldung.com/linux/openssh-internal-sftp-vs-sftp-server&quot;&gt;https://www.baeldung.com/linux/openssh-internal-sftp-vs-sftp-server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://gist.github.com/kjellski/5940875&quot;&gt;https://gist.github.com/kjellski/5940875&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://serverfault.com/questions/584986/bad-ownership-or-modes-for-chroot-directory-component&quot;&gt;https://serverfault.com/questions/584986/bad-ownership-or-modes-for-chroot-directory-component&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://www.flaticon.com/free-icons/sftp&quot; title=&quot;sftp icons&quot;&gt;Sftp icons created by Freepik - Flaticon&lt;/a&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>Python GUI</title>
<link href="https://www.0ink.net/posts/2024/2024-03-01-python-gui.html"></link>
<id>urn:uuid:038c7fed-7cc6-8b91-c7bc-a561e251520f</id>
<updated>2024-11-22T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
After looking a multiple options of GUI programming under python I
eventually settled for tkinter.  The main reason was that
tkinter is very ubiquitous and initially though the learning
curve wuld have shorter as I was very used to GUI programming using
TCL/TK.  Turned out that what I known TCL/TK did not translate
...]]></summary>
<content type="html">&lt;p&gt;&lt;img src=&quot;/images/2024/pygui.jpg&quot; alt=&quot;pygui&quot; /&gt;&lt;/p&gt;
&lt;p&gt;After looking a multiple options of GUI programming under &lt;a href=&quot;https://www.python.org/&quot;&gt;python&lt;/a&gt; I
eventually settled for &lt;a href=&quot;https://en.wikipedia.org/wiki/Tkinter&quot;&gt;tkinter&lt;/a&gt;.  The main reason was that
&lt;a href=&quot;https://en.wikipedia.org/wiki/Tkinter&quot;&gt;tkinter&lt;/a&gt; is very ubiquitous and initially though the learning
curve wuld have shorter as I was very used to GUI programming using
&lt;a href=&quot;https://www.tcl.tk/&quot;&gt;TCL/TK&lt;/a&gt;.  Turned out that what I known &lt;a href=&quot;https://www.tcl.tk/&quot;&gt;TCL/TK&lt;/a&gt; did not translate
very well to &lt;a href=&quot;https://en.wikipedia.org/wiki/Tkinter&quot;&gt;tkinter&lt;/a&gt; in &lt;a href=&quot;https://www.python.org/&quot;&gt;python&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Also I found out some &lt;strong&gt;BASIC&lt;/strong&gt; features that I was used to in &lt;a href=&quot;https://www.tcl.tk/&quot;&gt;TCL/TK&lt;/a&gt; were
not available in &lt;a href=&quot;https://www.python.org/&quot;&gt;python&lt;/a&gt;.  For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Implementing optional scrollbars&lt;/li&gt;
&lt;li&gt;Scrollable frames&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At some time I considered using &lt;a href=&quot;https://kivy.org/&quot;&gt;kivy&lt;/a&gt; but at the end, I did not.  Since the
main advantage for it is that you can create mobile apps.  But since my primary
phone is an iPhone, I don&#039;t think I would be able to create iPhone apps
due to Apple&#039;s walled garden restrictions.&lt;/p&gt;
&lt;p&gt;So try things out, I wrote a couiple of scripts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alejandroliu/0ink.net/tree/main/snippets/2020/pa-hints&quot;&gt;patoggle&lt;/a&gt; &lt;br /&gt;This one is not that interesting as it only shows things on the screen but does not
have any inputs.  But I though was a good starting project. &lt;br /&gt;&lt;img src=&quot;/images/2024/patoggle.png&quot; alt=&quot;patoggle screenshot&quot; /&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alejandroliu/0ink.net/tree/main/snippets/2023/xprtmgr&quot;&gt;xprtmgr&lt;/a&gt; &lt;br /&gt;This is a more complete application.  It was a good learning experience. &lt;br /&gt;&lt;img src=&quot;/images/2024/xprtmgr.png&quot; alt=&quot;xprtmgr screenshot&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I assume that as I get more experience, things should be easier.&lt;/p&gt;</content>
</entry>
<entry>
<title>cisco bridging</title>
<link href="https://www.0ink.net/posts/2024/2024-02-15-cisco.html"></link>
<id>urn:uuid:0aaef5d8-4a7b-055b-9ab1-fc58dbeb250b</id>
<updated>2024-11-22T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
This article is here as a reminder.
So, for testing, I needed to configure a
Cisco CSR1000V virtual router as a bridge.  So I used a version 16 Cisco
IOS XE image.  To make my life easier I used the &quot;wizard&quot; that runs the first
time to automatically configure bridgning. Ironically, this created an invalid
...]]></summary>
<content type="html">&lt;p&gt;&lt;img src=&quot;/images/2024/cisco_logo.png&quot; alt=&quot;cisco&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This article is here as a reminder.&lt;/p&gt;
&lt;p&gt;So, for testing, I needed to configure a
&lt;a href=&quot;https://www.cisco.com/c/en/us/products/routers/cloud-services-router-1000v-series/index.html&quot;&gt;Cisco CSR1000V virtual router&lt;/a&gt; as a bridge.  So I used a version 16 Cisco
IOS XE image.  To make my life easier I used the &amp;quot;wizard&amp;quot; that runs the first
time to automatically configure bridgning. Ironically, this created an invalid
configuration.&lt;/p&gt;
&lt;p&gt;Over the years, cisco has transitioned through multiple ways to configure bridging,
searching the Internet, it was not clear to me how to configure bridging.  Eventually
I manage to configure using bridge domains.  The configuration is as follows:&lt;/p&gt;
&lt;h2 id=&quot;Configure+spanning+tree+features&quot; name=&quot;Configure+spanning+tree+features&quot;&gt;Configure spanning tree features&lt;/h2&gt;
&lt;p&gt;These are cisco global settings.  For my test I was using the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;spanning-tree mode rapid-pvst
spanning-tree loopguard default
spanning-tree portfast bpduguard default
spanning-tree extend system-id&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Some of these setting are ON by default, so in some cases you don&#039;t need to.&lt;/p&gt;
&lt;h2 id=&quot;Configure+bridge+domains&quot; name=&quot;Configure+bridge+domains&quot;&gt;Configure bridge domains&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;bridge-domain 1
bridge-domain 200
bridge-domain 201&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Actually, these are not needed as they are automatically created when configuring
the bridge-domain interfaces.  However, these would show on the &lt;code&gt;running-config&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;Configure+bridge+members&quot; name=&quot;Configure+bridge+members&quot;&gt;Configure bridge members&lt;/h2&gt;
&lt;p&gt;For network interface, you need:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;interface GigabitEthernet1
 no ip address
 service instance 1 ethernet
  encapsulation dot1q 1
  bridge-domain 1
 !
 service instance 200 ethernet
  encapsulation dot1q 200
  bridge-domain 200
 !
 service instance 201 ethernet
  encapsulation dot1q 201
  bridge-domain 201
 !&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;The interface line for the given port that is part of the switch.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;no ip address&lt;/code&gt; : We are doing Layer-2, so no IP is needed.&lt;/li&gt;
&lt;li&gt;For each VLAN that we are bridging we need:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;service instance ID ethernet&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;encapsupation dot1q VLAN_ID&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bridge-domain ID&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Note that I made the VLAN_ID the same as the instance ID and the bridge-domain ID.
This is not necessary but makes things less confusing.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;encapsulation&lt;/code&gt; is used for VLAN tagging.  It is possible to use &lt;code&gt;encapsulation untagged&lt;/code&gt;.
However, Spanning Tree protocol doesn&#039;t run on the untagged VLAN.&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Tunneling NFS over SSH</title>
<link href="https://www.0ink.net/posts/2024/2024-02-01-nfs-over-ssh.html"></link>
<id>urn:uuid:394c86b9-55b2-509e-d8c6-2f12f4bbf4b4</id>
<updated>2024-11-22T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
This recipe is for tunneling NFS traffic over SSH.  This adds encryption
and Public Key authentication to otherwise insecure NFS traffic.
For this recipe to work, requires NFSv4.  Earlier versions were
not tested, but I expect not all the functionality to work.
server configuration
...]]></summary>
<content type="html">&lt;p&gt;&lt;img src=&quot;/images/2024/padlock.png&quot; alt=&quot;padlock&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This recipe is for tunneling NFS traffic over SSH.  This adds encryption
and Public Key authentication to otherwise insecure NFS traffic.&lt;/p&gt;
&lt;p&gt;For this recipe to work, requires NFSv4.  Earlier versions were
not tested, but I expect not all the functionality to work.&lt;/p&gt;
&lt;h2 id=&quot;server+configuration&quot; name=&quot;server+configuration&quot;&gt;server configuration&lt;/h2&gt;
&lt;p&gt;Install packages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;nfs-kernel-server&lt;/li&gt;
&lt;li&gt;ncat or netcat-openbsd&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Configure &lt;code&gt;/etc/exports&lt;/code&gt;.  Add a line:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;/export/path 127.0.0.1(insecure,... other options...)&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/export/path&lt;/code&gt;: File system to export&lt;/li&gt;
&lt;li&gt;&lt;code&gt;127.0.0.1&lt;/code&gt;: Loopback address, we only allow local connections.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;insecure&lt;/code&gt;: Normally, the NFS server only allows connections from ports
less than 1024.  This option removes that restriction.  We need this because
the &lt;code&gt;ssh&lt;/code&gt; traffic is running as a normal user.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Additional NFS export options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;rw&lt;/code&gt; : Allow read/write access&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sync&lt;/code&gt; : sync I/O (recommended to prevent data loss)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;no_subtree_check&lt;/code&gt; : When exporting full filesystem, this remove the subtree checks.
This has to do with the fact that &lt;code&gt;NFS&lt;/code&gt; uses &lt;code&gt;inodes&lt;/code&gt;.  This check is needed to
make sure that the &lt;code&gt;inode&lt;/code&gt; is within the exported filesystem sub-tree.  However
if you are exporting the entire filesystem, there should never be the case
that an &lt;code&gt;inode&lt;/code&gt; falls outside the &lt;code&gt;subtree&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;no_root_squash&lt;/code&gt; : allow root access&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mountpoint=/mount/path&lt;/code&gt; : Only export if a filesystem is mounted on &lt;code&gt;/mount/path&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Obtain a public/private key.  Either use &lt;code&gt;ssh-keygen&lt;/code&gt; or copy it from elsewhere.
Install &lt;code&gt;authorized_keys&lt;/code&gt; on the account to be used for SSH/TCP forwarding:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;command=&quot;nc -N  localhost 2049&quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This account does not need to be &lt;code&gt;root&lt;/code&gt;.  The additional settings make sure that this
key can only be used for forwarding traffic.&lt;/p&gt;
&lt;h2 id=&quot;client+configuration&quot; name=&quot;client+configuration&quot;&gt;client configuration&lt;/h2&gt;
&lt;p&gt;This is for &lt;a href=&quot;https://en.wikipedia.org/wiki/Ubuntu&quot;&gt;Ubuntu&lt;/a&gt;.  Other distros may need different packages
and/or approaches.&lt;/p&gt;
&lt;p&gt;Install packages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;nfs-common&lt;/li&gt;
&lt;li&gt;ncat (netcat-openbsd is not enough, ncat needs to support -e or -c)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Make sure the NFS server is in the SSH forwarder&#039;s &lt;code&gt;known_hosts&lt;/code&gt; file.  Use this command
to make sure this happens:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh -n -i $ssh_key -T \
        -o StrictHostKeyChecking=accept-new \
        -o BatchMode=yes \
        -o ConnectTimeout=10 \
        $nfs_server&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this approach we are using &lt;code&gt;ncat&lt;/code&gt; to implement &lt;code&gt;SSH&lt;/code&gt; forwarder.  Run this
command from the &lt;code&gt;/etc/rc.local&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;lport=4096
ssh_key=/path/to/ssh/private/key
ssh_opts=&quot;-o BatchMode=yes -o ConnectTimeout=10 -a -C&quot;
nfs_srv=nfs-server

( ncat -l $lport -k --allow localhost -c &quot;exec ssh -i $ssh_key $ssh_opts $nfs_srv&quot; ) &amp;amp;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-a&lt;/code&gt; : disable agent forwarding&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-C&lt;/code&gt; : request compression&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-T&lt;/code&gt; : disable pty&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;An alternative to this is to use &lt;code&gt;inetd.conf&lt;/code&gt; or use &lt;a href=&quot;http://0pointer.de/blog/projects/inetd.html&quot;&gt;systemd&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;At this point we are ready to mount NFS filesystems:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mount -t nfs \
  -o nfsvers=4,nolock,nosuid,nodev,port=4096,sec=sys,tcp,soft,intr,fg \
  localhost:/export/nfs/path \
  /mnt&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;nfsvers=4&lt;/code&gt; : make sure we are running NFSv4&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nosuid&lt;/code&gt; : disable SUID executables&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nodev&lt;/code&gt; : disable device files&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sec=sys&lt;/code&gt; : traditional UNIX security modes&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tcp&lt;/code&gt; : use TCP protocol&lt;/li&gt;
&lt;li&gt;&lt;code&gt;soft,intr&lt;/code&gt; : how we handle CTRL+C and other errors&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Caveats&quot; name=&quot;Caveats&quot;&gt;Caveats&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;showmount&lt;/code&gt; command does not show NFSv4 information.&lt;/li&gt;
&lt;li&gt;I have not tried to use this with &lt;code&gt;autofs&lt;/code&gt;.  &lt;code&gt;autofs&lt;/code&gt; configure for NFS shares is in
&lt;code&gt;/etc/autofs/auto.net&lt;/code&gt;, but it uses &lt;code&gt;showmount&lt;/code&gt; command, so it probably would not
work out of the box.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Notes&quot; name=&quot;Notes&quot;&gt;Notes&lt;/h2&gt;
&lt;p&gt;Some implementations of the &lt;code&gt;nc&lt;/code&gt; command supports a &lt;code&gt;-p source_port&lt;/code&gt; option.  This would
remove the need to use the &lt;code&gt;insecure&lt;/code&gt; option from the nfs export options.  However this
requires netcat to run as root.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;References:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/storage_administration_guide/s1-nfs-client-config-options&quot;&gt;Common NFS mount options&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/5/html/deployment_guide/s1-nfs-server-config-exports&quot;&gt;/etc/exports documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Ubuntu&quot;&gt;ubuntu&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://help.ubuntu.com/community/Autofs&quot;&gt;autofs help&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://help.ubuntu.com/community/NFSv4Howto&quot;&gt;Ubuntu NFSv4 HOWTO&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;a href=&quot;https://www.flaticon.com/free-icons/lock&quot; title=&quot;lock icons&quot;&gt;Lock icons created by Freepik - Flaticon&lt;/a&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>Optimizing shell scripts</title>
<link href="https://www.0ink.net/posts/2024/2024-01-15-optimiz.html"></link>
<id>urn:uuid:492207d2-c5cb-f054-5233-5cb2048f981a</id>
<updated>2024-03-05T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Introduction
Input Data
Desired Output
Approach
Original Script
...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Introduction&quot;&gt;Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Input+Data&quot;&gt;Input Data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Desired+Output&quot;&gt;Desired Output&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Approach&quot;&gt;Approach&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Original+Script&quot;&gt;Original Script&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Optimized+Script&quot;&gt;Optimized Script&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Moving+invariant+code+out+of+loops&quot;&gt;Moving invariant code out of loops&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Replacing+if%2Fthen%2Felse+with+case&quot;&gt;Replacing if/then/else with case&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Using+IFS+for+parsing&quot;&gt;Using IFS for parsing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;a href=&quot;https://bashlogo.com/&quot;&gt;&lt;img src=&quot;/images/2024/bash_logo.png&quot; alt=&quot;Bash Logo&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;Introduction&quot; name=&quot;Introduction&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;I consider myself a fairly competent shell scripter.  I typically prefer
to program towards readabilty, but at the end tend to write towards
terse code.&lt;/p&gt;
&lt;p&gt;Usually, readability is important because other people (including myself
in the future) will need to read the code and figure out what is going
on.&lt;/p&gt;
&lt;p&gt;I think because I used to write &lt;a href=&quot;https://en.wikipedia.org/wiki/Perl&quot;&gt;perl&lt;/a&gt; for a long time, I tend also
to write fairly terse code.  This is not something to be proud of.&lt;/p&gt;
&lt;p&gt;The other day, I was writing a small shell script to generate a menu of
links in &lt;a href=&quot;https://en.wikipedia.org/wiki/Markdown&quot;&gt;markdown&lt;/a&gt; from a simple specially written text file.&lt;/p&gt;
&lt;h2 id=&quot;Input+Data&quot; name=&quot;Input+Data&quot;&gt;Input Data&lt;/h2&gt;
&lt;p&gt;See input:&lt;/p&gt;
&lt;script src=&quot;https://tortugalabs.github.io/embed-like-gist/embed.js?style=github&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showCopy=on&amp;amp;fetchFromJsDelivr=on&amp;amp;target=https://github.com/alejandroliu/0ink.net/blob/main/snippets/2024/optimiz/input.txt&quot;&gt;&lt;/script&gt;
&lt;h2 id=&quot;Desired+Output&quot; name=&quot;Desired+Output&quot;&gt;Desired Output&lt;/h2&gt;
&lt;p&gt;See output:&lt;/p&gt;
&lt;script src=&quot;https://tortugalabs.github.io/embed-like-gist/embed.js?style=github&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showCopy=on&amp;amp;fetchFromJsDelivr=on&amp;amp;target=https://github.com/alejandroliu/0ink.net/blob/main/snippets/2024/optimiz/output.md&quot;&gt;&lt;/script&gt;
&lt;h2 id=&quot;Approach&quot; name=&quot;Approach&quot;&gt;Approach&lt;/h2&gt;
&lt;p&gt;I initially wrote it on my PC with a comparatively faster CPU.  The response
time on my PC was about one second.  When I transferred the script
to my Web server (with an small CPU), the reponse time was 15 seconds to
generate the menu.  This was surprising to me, as I did not expect the
performance difference between my PC and my Web server to be so massive.&lt;/p&gt;
&lt;p&gt;I figured that this need to be optimized.  The simplest way to do this is to
add caching.  Which I did very quickly.  In reality, I needed to improve the code.&lt;/p&gt;
&lt;p&gt;The first thing you need to do when optimizing code is to measure the effects of
changes on the code.  In order to do that, I created a simple test harness:&lt;/p&gt;
&lt;script src=&quot;https://tortugalabs.github.io/embed-like-gist/embed.js?style=github&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showCopy=on&amp;amp;fetchFromJsDelivr=on&amp;amp;target=https://github.com/alejandroliu/0ink.net/blob/main/snippets/2024/optimiz/th.cgi&quot;&gt;&lt;/script&gt;
&lt;p&gt;The options to the harness are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-1&lt;/code&gt; or &lt;code&gt;--no-count&lt;/code&gt; : run the code once.  Essentially to check that the output is correct.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--count=int&lt;/code&gt; : Will run the code the specified number of times&lt;/li&gt;
&lt;li&gt;&lt;code&gt;1&lt;/code&gt; or &lt;code&gt;2&lt;/code&gt; : Run implementation 1 or implementation 2.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Run this with the &lt;code&gt;time&lt;/code&gt; built-in to measure running times.&lt;/p&gt;
&lt;p&gt;So running:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sh th.cgi -1 1&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sh th.cgi -1 2&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Can be used to make sure that implementation &lt;strong&gt;1&lt;/strong&gt; and &lt;strong&gt;2&lt;/strong&gt; are correct.&lt;/p&gt;
&lt;p&gt;Then, you can run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;time sh th.cgi --count=100 1&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;time sh th.cgi --count=100 2&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will show the how fast/slow the code peforms.  For testing, I would also
test with &lt;code&gt;busybox sh&lt;/code&gt;, as that is the &lt;code&gt;shell&lt;/code&gt; that I have in my web server.  On
my PC, &lt;code&gt;sh&lt;/code&gt; is &lt;a href=&quot;https://en.wikipedia.org/wiki/Bash_(Unix_shell)&quot;&gt;bash&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So the general approach is to measure the original performance, then make some
changes and measure if the performance improves (or not).&lt;/p&gt;
&lt;h2 id=&quot;Original+Script&quot; name=&quot;Original+Script&quot;&gt;Original Script&lt;/h2&gt;
&lt;p&gt;The original un-optimized code is:&lt;/p&gt;
&lt;script src=&quot;https://tortugalabs.github.io/embed-like-gist/embed.js?style=github&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showCopy=on&amp;amp;fetchFromJsDelivr=on&amp;amp;target=https://github.com/alejandroliu/0ink.net/blob/main/snippets/2024/optimiz/th1.cgi&quot;&gt;&lt;/script&gt;
&lt;p&gt;Running this on my PC executes in almost 10 minutes.&lt;/p&gt;
&lt;h2 id=&quot;Optimized+Script&quot; name=&quot;Optimized+Script&quot;&gt;Optimized Script&lt;/h2&gt;
&lt;p&gt;The optimized version is:&lt;/p&gt;
&lt;script src=&quot;https://tortugalabs.github.io/embed-like-gist/embed.js?style=github&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showCopy=on&amp;amp;fetchFromJsDelivr=on&amp;amp;target=https://github.com/alejandroliu/0ink.net/blob/main/snippets/2024/optimiz/th2.cgi&quot;&gt;&lt;/script&gt;
&lt;p&gt;This version executes in 16 seconds.&lt;/p&gt;
&lt;p&gt;So overall is a pretty good performance enhancement.  So the approach I took is first to isolate
what part of the code is the one that executes the slowest.  This script essentially runs in
a loop and runs on two halves, the first half is scanning lines, the second half is outputing
markdown.&lt;/p&gt;
&lt;p&gt;Commenting out the different parts of the code, I was able to determine that most of the
time was spent in the scanning half.&lt;/p&gt;
&lt;p&gt;The next step was to review the code and re-factor it to faster versions:&lt;/p&gt;
&lt;h3 id=&quot;Moving+invariant+code+out+of+loops&quot; name=&quot;Moving+invariant+code+out+of+loops&quot;&gt;Moving invariant code out of loops&lt;/h3&gt;
&lt;p&gt;Move invariable code outside the loop.  Assigning &lt;code&gt;elem2o&lt;/code&gt; and &lt;code&gt;elem2c&lt;/code&gt; was
originally done in the loop.  This was moved outside the loop.  This did not yield
much improvement, but in general this is always a good optimizaiton.&lt;/p&gt;
&lt;h3 id=&quot;Replacing+if%2Fthen%2Felse+with+case&quot; name=&quot;Replacing+if%2Fthen%2Felse+with+case&quot;&gt;Replacing if/then/else with case&lt;/h3&gt;
&lt;p&gt;I was doing:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;if (echo &quot;$ARGS&quot; | grep -q &#039;&amp;gt;&#039;) ; then
  ...
else
  ...
fi&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This actually forks and execs two additional commands and a subshell.  This was replaced with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;case &quot;$ARGS&quot; in
*\&amp;gt;*)
  ...
  ;;
*)
  ...
  ;;
esac&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since this runs entirely inside the shell, this removes spawning commands and forking subshells.&lt;/p&gt;
&lt;h3 id=&quot;Using+IFS+for+parsing&quot; name=&quot;Using+IFS+for+parsing&quot;&gt;Using IFS for parsing&lt;/h3&gt;
&lt;p&gt;I replaced:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;local i=1
while [ -n &quot;$(echo &quot;$ARGS&quot; | cut -d&#039;&amp;gt;&#039; -f$i-)&quot; ] ; do
  set - &quot;$@&quot; &quot;$(echo &quot;$ARGS&quot; | cut -d&#039;&amp;gt;&#039; -f$i | xargs)&quot;
  i=$(expr $i + 1)
done&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which is a terrible way to parse a line.  This was replaced with:&lt;/p&gt;
&lt;p&gt;Adding &lt;code&gt;-e &#039;s/[     ]*&amp;gt;[    ]*/&amp;gt;/g&#039;&lt;/code&gt; to the &lt;code&gt;sed&lt;/code&gt; command at the top in front of
the loop, and then...&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;IFS=&quot;&amp;gt;&quot; ; set - $ARGS; IFS=&quot;$oIFS&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The performance difference here is &lt;em&gt;massive&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;Conclusion&quot; name=&quot;Conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;So there you have it.  Turns out that my skills at &lt;code&gt;scripting&lt;/code&gt; are poor.  On the other
hand it could be that I am so used writing shell scripts that I am not using the best
tools for the job.  In this particular example, I shouldn&#039;t have started with shell
script but use something more suitable for text manipulation, such as &lt;a href=&quot;https://en.wikipedia.org/wiki/Perl&quot;&gt;perl&lt;/a&gt; or
&lt;a href=&quot;https://en.wikipedia.org/wiki/AWK&quot;&gt;awk&lt;/a&gt;.  However, most scripting languages come with useful string processing
capabilities and would have done the job well.&lt;/p&gt;</content>
</entry>
<entry>
<title>Happy New Year 2024</title>
<link href="https://www.0ink.net/posts/2024/2024-01-01-new-year.html"></link>
<id>urn:uuid:37d41fbf-2a6c-997e-cbc8-529ef895dff3</id>
<updated>2024-11-22T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Best wishes for 2024!
...]]></summary>
<content type="html">&lt;p&gt;&lt;img src=&quot;/images/2024/newyear-2024.png&quot; alt=&quot;NewYear2024&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Best wishes for 2024!&lt;/p&gt;</content>
</entry>
<entry>
<title>IPv6 on 2023</title>
<link href="https://www.0ink.net/posts/2023/2023-12-15-ipv6-2023.html"></link>
<id>urn:uuid:e5620601-9263-1c06-e2f8-eb77cfed3187</id>
<updated>2024-11-22T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[This is a sequel to my article IPv6 blues.


Layout
Enabling forwarding
Configure networking
...]]></summary>
<content type="html">&lt;p&gt;This is a sequel to my article &lt;a href=&quot;/posts/2021/2021-12-21-ipv6-blues.html&quot;&gt;IPv6 blues&lt;/a&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Layout&quot;&gt;Layout&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Enabling+forwarding&quot;&gt;Enabling forwarding&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Configure+networking&quot;&gt;Configure networking&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Prefix+delegation&quot;&gt;Prefix delegation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Router+advertisement&quot;&gt;Router advertisement&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;At the time it looked that only /64 prefix address was being allocated.
However, when I recently checked my ADSL modem router configuration I
noticed that actually the ADSL modem gets assigned /48 prefix.  Thids makes
the configuration &lt;em&gt;much&lt;/em&gt; easier.&lt;/p&gt;
&lt;p&gt;What is nice, is that for &lt;strong&gt;&amp;quot;reasonable&amp;quot;&lt;/strong&gt; configurations, IPv6 usually does the
right thing and configures most things by itself.  For IPv6 routing, you only
need to enable the right functionality and most of the addressing is determined
automatically.&lt;/p&gt;
&lt;p&gt;This article expands on this
&lt;a href=&quot;https://wiki.alpinelinux.org/wiki/Linux_Router_with_VPN_on_a_Raspberry_Pi_(IPv6)&quot;&gt;Alpine Linux Wiki article&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;Layout&quot; name=&quot;Layout&quot;&gt;Layout&lt;/h2&gt;
&lt;p&gt;This is a very simple configuration.&lt;/p&gt;
&lt;div&gt;&lt;svg class=&quot;bob&quot; font-family=&quot;arial&quot; font-size=&quot;14&quot; height=&quot;64&quot; width=&quot;456&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;defs&gt;
&lt;marker id=&quot;triangle&quot; markerHeight=&quot;10&quot; markerUnits=&quot;strokeWidth&quot; markerWidth=&quot;10&quot; orient=&quot;auto&quot; refX=&quot;15&quot; refY=&quot;10&quot; viewBox=&quot;0 0 50 20&quot;&gt;
&lt;path d=&quot;M 0 0 L 30 10 L 0 20 z&quot;/&gt;
&lt;/marker&gt;
&lt;/defs&gt;
&lt;style&gt;

    line, path {
      stroke: black;
      stroke-width: 2;
      stroke-opacity: 1;
      fill-opacity: 1;
      stroke-linecap: round;
      stroke-linejoin: miter;
    }
    circle {
      stroke: black;
      stroke-width: 2;
      stroke-opacity: 1;
      fill-opacity: 1;
      stroke-linecap: round;
      stroke-linejoin: miter;
    }
    circle.solid {
      fill:black;
    }
    circle.open {
      fill:transparent;
    }
    tspan.head{
        fill: none;
        stroke: none;
    }
    
&lt;/style&gt;
&lt;path d=&quot; M 4 24 L 8 24 M 4 24 L 4 32 M 8 24 L 16 24 M 8 24 L 16 24 L 24 24 M 16 24 L 24 24 L 32 24 M 24 24 L 32 24 L 40 24 M 32 24 L 40 24 L 48 24 M 40 24 L 48 24 L 56 24 M 48 24 L 56 24 L 64 24 M 56 24 L 64 24 L 72 24 M 64 24 L 72 24 L 80 24 M 72 24 L 80 24 L 88 24 M 80 24 L 88 24 M 92 24 L 88 24 M 92 24 L 92 32 M 196 24 L 200 24 M 196 24 L 196 32 M 200 24 L 208 24 M 200 24 L 208 24 L 216 24 M 208 24 L 216 24 L 224 24 M 216 24 L 224 24 L 232 24 M 224 24 L 232 24 L 240 24 M 232 24 L 240 24 L 248 24 M 240 24 L 248 24 M 252 24 L 248 24 M 252 24 L 252 32 M 332 24 L 336 24 M 332 24 L 332 32 M 336 24 L 344 24 M 336 24 L 344 24 L 352 24 M 344 24 L 352 24 L 360 24 M 352 24 L 360 24 L 368 24 M 360 24 L 368 24 L 376 24 M 368 24 L 376 24 L 384 24 M 376 24 L 384 24 L 392 24 M 384 24 L 392 24 L 400 24 M 392 24 L 400 24 L 408 24 M 400 24 L 408 24 L 416 24 M 408 24 L 416 24 L 424 24 M 416 24 L 424 24 L 432 24 M 424 24 L 432 24 L 440 24 M 432 24 L 440 24 L 448 24 M 440 24 L 448 24 M 452 24 L 448 24 M 452 24 L 452 32 M 4 32 L 4 48 M 4 32 L 4 48 M 92 32 L 92 48 M 92 32 L 92 48 M 104 40 L 92 40 M 104 40 L 112 40 M 104 40 L 112 40 L 120 40 M 112 40 L 120 40 L 128 40 M 120 40 L 128 40 L 136 40 M 128 40 L 136 40 L 144 40 M 136 40 L 144 40 L 152 40 M 144 40 L 152 40 L 160 40 M 152 40 L 160 40 L 168 40 M 160 40 L 168 40 L 176 40 M 168 40 L 176 40 L 196 40 M 176 40 L 196 40 M 184 40 L 196 40 M 196 32 L 196 48 M 196 32 L 196 48 M 252 32 L 252 48 M 252 32 L 252 48 M 264 40 L 252 40 M 264 40 L 272 40 M 264 40 L 272 40 L 280 40 M 272 40 L 280 40 L 288 40 M 280 40 L 288 40 L 296 40 M 288 40 L 296 40 L 304 40 M 296 40 L 304 40 L 312 40 M 304 40 L 312 40 L 332 40 M 312 40 L 332 40 M 320 40 L 332 40 M 332 32 L 332 48 M 332 32 L 332 48 M 452 32 L 452 48 M 452 32 L 452 48 M 4 56 L 4 48 M 4 56 L 8 56 L 16 56 M 8 56 L 16 56 L 24 56 M 16 56 L 24 56 L 32 56 M 24 56 L 32 56 L 40 56 M 32 56 L 40 56 L 48 56 M 40 56 L 48 56 L 56 56 M 48 56 L 56 56 L 64 56 M 56 56 L 64 56 L 72 56 M 64 56 L 72 56 L 80 56 M 72 56 L 80 56 L 88 56 M 80 56 L 88 56 M 92 56 L 92 48 M 92 56 L 88 56 M 196 56 L 196 48 M 196 56 L 200 56 L 208 56 M 200 56 L 208 56 L 216 56 M 208 56 L 216 56 L 224 56 M 216 56 L 224 56 L 232 56 M 224 56 L 232 56 L 240 56 M 232 56 L 240 56 L 248 56 M 240 56 L 248 56 M 252 56 L 252 48 M 252 56 L 248 56 M 332 56 L 332 48 M 332 56 L 336 56 L 344 56 M 336 56 L 344 56 L 352 56 M 344 56 L 352 56 L 360 56 M 352 56 L 360 56 L 368 56 M 360 56 L 368 56 L 376 56 M 368 56 L 376 56 L 384 56 M 376 56 L 384 56 L 392 56 M 384 56 L 392 56 L 400 56 M 392 56 L 400 56 L 408 56 M 400 56 L 408 56 L 416 56 M 408 56 L 416 56 L 424 56 M 416 56 L 424 56 L 432 56 M 424 56 L 432 56 L 440 56 M 432 56 L 440 56 L 448 56 M 440 56 L 448 56 M 452 56 L 452 48 M 452 56 L 448 56&quot; fill=&quot;none&quot;/&gt;
&lt;path d=&quot;&quot; fill=&quot;none&quot; stroke-dasharray=&quot;3 3&quot;/&gt;
&lt;text x=&quot;153&quot; y=&quot;28&quot;&gt;
eth0
&lt;/text&gt;
&lt;text x=&quot;9&quot; y=&quot;44&quot;&gt;
KPN
&lt;/text&gt;
&lt;text x=&quot;41&quot; y=&quot;44&quot;&gt;
modem
&lt;/text&gt;
&lt;text x=&quot;201&quot; y=&quot;44&quot;&gt;
router
&lt;/text&gt;
&lt;text x=&quot;337&quot; y=&quot;44&quot;&gt;
HOME
&lt;/text&gt;
&lt;text x=&quot;385&quot; y=&quot;44&quot;&gt;
NETWORK
&lt;/text&gt;
&lt;text x=&quot;265&quot; y=&quot;60&quot;&gt;
eth1
&lt;/text&gt;
&lt;/svg&gt;
&lt;/div&gt;
&lt;p&gt;The current Alpine Linux kernel (v3.17.3) has IPv6 enabled by default, so nothing
special needs to be done for that.&lt;/p&gt;
&lt;h2 id=&quot;Enabling+forwarding&quot; name=&quot;Enabling+forwarding&quot;&gt;Enabling forwarding&lt;/h2&gt;
&lt;p&gt;By deault, IP (v4 or v6) forward is disabled on a Linux kernel.  To enable, you
need to modify &lt;code&gt;syctl.conf&lt;/code&gt;.  Create a file:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/etc/sysctl.d/router.conf&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Controls IP packet forwarding
net.ipv4.ip_forward = 1

# http://vk5tu.livejournal.com/37206.html
# What&#039;s this special value &quot;2&quot;? Originally the value was &quot;1&quot;, but this 
# disabled autoconfiguration on all interfaces. That is, you couldn&#039;t appear 
# to be a router on some interfaces and appear to be a host on other 
# interfaces. But that&#039;s exactly the mental model of a ADSL router. 

# Controls IP packet forwarding
net.ipv6.conf.all.forwarding = 2
net.ipv6.conf.default.forwarding = 2

# Accept Router Advertisments
net.ipv6.conf.all.accept_ra = 2
net.ipv6.conf.default.accept_ra = 2

# We are a router so disable temporary addresses
net.ipv6.conf.all.use_tempaddr = 0
net.ipv6.conf.default.use_tempaddr = 0
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Configure+networking&quot; name=&quot;Configure+networking&quot;&gt;Configure networking&lt;/h2&gt;
&lt;p&gt;Configure IPv6 address for &lt;code&gt;eth1&lt;/code&gt;.  We don&#039;t need to configure &lt;code&gt;eth0&lt;/code&gt; as that will be
done by &lt;code&gt;dhcpcd&lt;/code&gt; through the ISP&#039;s router advertisements:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Conect to ISP
auto eth0
iface eth0 inet static
  address 192.168.2.250
  netmask 255.255.255.0
  broadcast 192.168.2.255

# Connected to local LAN
auto eth1
iface eth1 inet static
  address 192.168.3.1
  netmask 255.255.255.0
  broadcast 192.168.3.255

iface eth1 inet6 static
  address fde4:8dba:82e1:fff4::1
  netmask 64
  autoconf 0
  accept_ra 0
  privext 0&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Prefix+delegation&quot; name=&quot;Prefix+delegation&quot;&gt;Prefix delegation&lt;/h2&gt;
&lt;p&gt;The next step will be to configure DHCPv6 Prefix Delegation with your ISP.
Install &lt;code&gt;dhcpcd&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apk add dhcpcd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Configure it:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/etc/dhcpcd.conf&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
# Enable extra debugging
#debug
#logfile /var/log/dhcpcd.log

# Allow users of this group to interact with dhcpcd via the control
# socket.
#controlgroup wheel

# Inform the DHCP server of our hostname for DDNS.
hostname gateway

# Use the hardware address of the interface for the Client ID.
#clientid
# or
# Use the same DUID + IAID as set in DHCPv6 for DHCPv4 ClientID as
# per RFC4361. Some non-RFC compliant DHCP servers do not reply with
# this set. In this case, comment out duid and enable clientid above.
duid

# Persist interface configuration when dhcpcd exits.
persistent

# Rapid commit support.
# Safe to enable by default because it requires the equivalent option
# set on the server to actually work.
option rapid_commit

# A list of options to request from the DHCP server.
option domain_name_servers, domain_name, domain_search, host_name
option classless_static_routes

# Most distributions have NTP support.
option ntp_servers

# Respect the network MTU.
# Some interface drivers reset when changing the MTU so disabled by
# default.
#option interface_mtu

# A ServerID is required by RFC2131.
require dhcp_server_identifier

# Generate Stable Private IPv6 Addresses instead of hardware based
# ones
slaac private

# A hook script is provided to lookup the hostname if not set by the
# DHCP server, but it should not be run by default.
nohook lookup-hostname

# IPv6 Only
ipv6only

# Disable solicitations on all interfaces
noipv6rs

# Wait for IP before forking to background
waitip 6

# Don&#039;t touch DNS
nohook resolv.conf

# Use the interface connected to WAN
interface eth0
    ipv6rs # enable routing solicitation get the default IPv6 route
    iaid 1
    ia_pd 1/::/56 eth1/2/64&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add dhcpcd to the default run level:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rc-update add dhcpcd default&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Router+advertisement&quot; name=&quot;Router+advertisement&quot;&gt;Router advertisement&lt;/h2&gt;
&lt;p&gt;Now we need to configure &lt;code&gt;radvd&lt;/code&gt; to give router advertisements to our internal
network for addressing and routing.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apk add radvd&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once &lt;code&gt;radvd&lt;/code&gt; is installed, you may configure it:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/etc/radvd.conf&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
interface eth0 {

  # We are sending advertisements (route)
  AdvSendAdvert on;

  # When set, host use the administered (stateful) protocol
  # for address autoconfiguration. The use of this flag is
  # described in RFC 4862
  AdvManagedFlag on;

  # When set, host use the administered (stateful) protocol
  # for address autoconfiguration. For other (non-address)
  # information.
  # The use of this flag is described in RFC 4862
  AdvOtherConfigFlag on;

  # Suggested Maximum Transmission setting for using the
  # Hurricane Electric Tunnel Broker.
  # AdvLinkMTU 1480;

  # We have native Dual Stack IPv6 so we can use the regular MTU
  # http://blogs.cisco.com/enterprise/ipv6-mtu-gotchas-and-other-icmp-issues
  AdvLinkMTU 1500;

  prefix ::/64 {
    AdvOnLink on;
    AdvAutonomous on; ## SLAAC based on EUI
    AdvRouterAddr on;
  };
};

interface eth1 {

  AdvSendAdvert on;
  AdvManagedFlag on;
  AdvOtherConfigFlag on;
  AdvLinkMTU 1500;

  # Helps the route not get lost when on WiFi with packet loss
  MaxRtrAdvInterval 30;
  AdvDefaultLifetime 9000;

  prefix fde4:8dba:82e1:fff3::/64 {
    AdvOnLink on;
    AdvAutonomous on; ## SLAAC based on EUI
  };
};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add &lt;code&gt;radvd&lt;/code&gt; to the default run level:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rc-update add radvd default&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Conclusion&quot; name=&quot;Conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;At this point you should have a working IPv6 set-up.  Things that you may want to add:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Firewall rules&lt;/li&gt;
&lt;li&gt;Additional static routes and subnets&lt;/li&gt;
&lt;li&gt;DHCP daemon configuration to have more control on IP address assignments&lt;/li&gt;
&lt;li&gt;OpenVPN&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>libnss-db HOWTO</title>
<link href="https://www.0ink.net/posts/2023/2023-12-01-libnss-db.html"></link>
<id>urn:uuid:f41cfd34-2ed5-5533-5d25-089b2e27e4a4</id>
<updated>2023-09-05T00:00:00+02:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This mini-howto illustrate how to use  libnss-db on a Ubuntu
Linux system.
Other installations should work to after adjusting package names and directory paths.
I myself use as a &quot;serverless&quot; lightweight user directory.  Essentially, I mount the
db directory and the home directory from an NFS server.
Package installation
...]]></summary>
<content type="html">&lt;p&gt;This mini-howto illustrate how to use  &lt;code&gt;libnss-db&lt;/code&gt; on a &lt;a href=&quot;https://en.wikipedia.org/wiki/Ubuntu&quot;&gt;Ubuntu&lt;/a&gt;
Linux system.&lt;/p&gt;
&lt;p&gt;Other installations should work to after adjusting package names and directory paths.&lt;/p&gt;
&lt;p&gt;I myself use as a &amp;quot;serverless&amp;quot; lightweight user directory.  Essentially, I mount the
db directory and the home directory from an NFS server.&lt;/p&gt;
&lt;h2 id=&quot;Package+installation&quot; name=&quot;Package+installation&quot;&gt;Package installation&lt;/h2&gt;
&lt;p&gt;Install the following packages:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;apt install -y libnss-db make
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This creates a directory &lt;code&gt;/var/lib/misc&lt;/code&gt; in &lt;a href=&quot;https://en.wikipedia.org/wiki/Ubuntu&quot;&gt;Ubuntu&lt;/a&gt;.  Other distributions
this may be in &lt;code&gt;/var/db&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;Preparing+nss-db+data+directory&quot; name=&quot;Preparing+nss-db+data+directory&quot;&gt;Preparing nss-db data directory&lt;/h2&gt;
&lt;p&gt;I like to keep a separate set of users in the &lt;code&gt;db&lt;/code&gt; files. For that, create a
directory  &lt;code&gt;/var/lib/misc/etc&lt;/code&gt;.  This will contain the additional users and groups.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;for f in passwd group shadow
do
  cp -av /etc/$f /var/lib/misc/etc/$f
  &amp;gt;  /var/lib/misc/etc/$f
done&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This creates, &lt;code&gt;passwd&lt;/code&gt;, &lt;code&gt;group&lt;/code&gt;, and &lt;code&gt;shadow&lt;/code&gt; files.  &lt;strong&gt;NOTE:&lt;/strong&gt; &lt;code&gt;gshadow&lt;/code&gt;
is unsupported.  This is only relevant if you are using passwords to
control user group changes.&lt;/p&gt;
&lt;p&gt;Because in this scenario, we are using flat files (i.e. &lt;code&gt;/etc/passwd&lt;/code&gt; vs.
&lt;code&gt;/var/lib/misc/passwd.db&lt;/code&gt;) and db files, we want to not have uid/gid overlaps.&lt;/p&gt;
&lt;p&gt;Copy &lt;code&gt;/etc/login.defs&lt;/code&gt; to &lt;code&gt;/var/lib/misc/etc/login.defs&lt;/code&gt; and
change UID_MIN,UID_MAX,GID_MIN,GID_MAX to a different space (so as not
to overlap with the flatfiles spaces).&lt;/p&gt;
&lt;p&gt;When users are created with the &lt;code&gt;useradd&lt;/code&gt; command, you can pass the
&lt;code&gt;--prefix /var/lib/misc&lt;/code&gt; argument, so then it would create users in
the &lt;code&gt;/var/lib/misc/etc&lt;/code&gt; directory (ignoring &lt;code&gt;/etc&lt;/code&gt;) and would get
defaults from &lt;code&gt;/var/lib/misc/etc/login.defs&lt;/code&gt; (instead of &lt;code&gt;/etc/login.defs&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;For &lt;code&gt;/home&lt;/code&gt; directories to be created properly I create the symlink:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ln -s /home /var/lib/misc/home&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I also like to add &lt;code&gt;sudoers&lt;/code&gt; configuration to the &lt;code&gt;/var/lib/misc&lt;/code&gt; directory (so
it can be shared via NFS)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cp -av /etc/sudoers.d $dbdir&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Moving+nss-db+data&quot; name=&quot;Moving+nss-db+data&quot;&gt;Moving nss-db data&lt;/h2&gt;
&lt;p&gt;If you are storing nss-db in a different location, you can use a &lt;code&gt;mount --bind&lt;/code&gt;
to make it available in &lt;code&gt;/var/lib/misc&lt;/code&gt;.  This can be configured on &lt;code&gt;/etc/fstab&lt;/code&gt;
as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# /etc/fstab
/mount/point/dir /var/lib/misc none defaults,bind 0 0&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Configuring+nss-db&quot; name=&quot;Configuring+nss-db&quot;&gt;Configuring nss-db&lt;/h2&gt;
&lt;p&gt;In &lt;code&gt;/var/lib/misc&lt;/code&gt; there is a &lt;code&gt;Makefile&lt;/code&gt; that is used to create the relevant
&lt;code&gt;db&lt;/code&gt; files.  To control how this you can configure things in
&lt;code&gt;/etc/default/libnss-db&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# /etc/default/libnss-db
# settings for libnss-db

# Directory where the databases are kept
VAR_DB = /var/lib/misc
# Location of files
ETC = $(VAR_DB)/etc

# Databases to generate
DBS = passwd group shadow

# Programs used
AWK = awk
MAKEDB = makedb --quiet&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You must also add the &lt;code&gt;db&lt;/code&gt; setting to the &lt;code&gt;/etc/nsswitch.conf&lt;/code&gt; lines
for &lt;code&gt;passwd&lt;/code&gt;, &lt;code&gt;group&lt;/code&gt; and &lt;code&gt;shadow&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Configure nsswitch.conf
sed -i~ \
        -e &#039;s/^\(passwd:[ \t]*\).*$/\1files systemd db/&#039; \
        -e &#039;s/^\(group:[ \t]*\).*$/\1files systemd db/&#039; \
        -e &#039;s/^\(shadow:[ \t]*\).*$/\1files db/&#039; \
        /etc/nsswitch.conf&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is not strictly part of nss-db, but I like to add to &lt;code&gt;/etc/sudoers&lt;/code&gt;
the line:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@includedir /var/lib/misc/sudoers.d&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From then on, adding, modifying and removing users/groups should be done
on the files in &lt;code&gt;/var/lib/misc/etc&lt;/code&gt;.  Afterwards, use &lt;code&gt;make&lt;/code&gt; in &lt;code&gt;/var/lib/misc&lt;/code&gt;
to recrate &lt;code&gt;db&lt;/code&gt; files.&lt;/p&gt;
&lt;p&gt;For convenience I created a script in &lt;code&gt;/usr/local/bin&lt;/code&gt; named &lt;code&gt;nssdb&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/sh
#
# NSSDB command
#
nssdb_dir=/var/lib/misc
nssdb_opts=&quot;--prefix $nssdb_dir&quot;
extra_group_add_opts=&quot;--key GID_MIN=13000 --key GID_MAX=13999&quot;

if [ $# -eq 0 ] ; then
  cat &amp;lt;&amp;lt;-_EOF_
        Usage: $0 useradd|userdel|usermod|groupadd|groupdel|groupmod [options]
        _EOF_
  exit 1
fi

case &quot;$1&quot; in
  useradd|userdel|usermod|groupdel|groupmod)
    op=&quot;$1&quot; ; shift
    ;;
  groupadd)
    op=&quot;$1&quot; ; shift
    nssdb_opts=&quot;$nssdb_opts $extra_group_add_opts&quot;
    ;;
  *) echo &quot;$1: Unknown sub-command&quot; ; exit 1
esac

&quot;$op&quot; $nssdb_opts &quot;$@&quot; &amp;amp;&amp;amp; ( cd $nssdb_dir &amp;amp;&amp;amp; make )&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What it does is that you can run:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;nssdb useradd&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nssdb usermod&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nssdb userdel&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nssdb groupadd&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nssdb groupmod&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nssdb groupdel&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This will run the specified commands but with &lt;code&gt;--prefix&lt;/code&gt; option so these
will modify files in &lt;code&gt;/var/lib/misc&lt;/code&gt; and the &lt;code&gt;Makefile&lt;/code&gt; called accordingly.&lt;/p&gt;
&lt;p&gt;Note the following commands can not be supported:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;chfn&lt;/code&gt;: does not support &lt;code&gt;--prefix&lt;/code&gt; or &lt;code&gt;--root&lt;/code&gt; options.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chsh&lt;/code&gt;, &lt;code&gt;passwd&lt;/code&gt;, &lt;code&gt;newusers&lt;/code&gt; : only support &lt;code&gt;--root&lt;/code&gt; which requires &lt;code&gt;root&lt;/code&gt;
priviledges.&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Alpine Linux Custom Interface names</title>
<link href="https://www.0ink.net/posts/2023/2023-11-15-alpine-linux-custom-if-names.html"></link>
<id>urn:uuid:843f0670-7abe-b346-ec3f-3a1bfd9deccc</id>
<updated>2023-09-29T00:00:00+02:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This article is a copy of this article
and  shows how to rename/change name of a network interface.
Alpine Linux uses busybox mdev to manage devices in /dev. mdev reads /etc/mdev.conf
and according to mdev documentation one
can define a command to be executed per device definition.
The command which is going to be used to change network interface name is nameif.
...]]></summary>
<content type="html">&lt;p&gt;This article is a copy of &lt;a href=&quot;https://wiki.alpinelinux.org/wiki/Custom_network_interface_names&quot;&gt;this article&lt;/a&gt;
and  shows how to rename/change name of a network interface.&lt;/p&gt;
&lt;p&gt;Alpine Linux uses &lt;code&gt;busybox&lt;/code&gt; &lt;code&gt;mdev&lt;/code&gt; to manage devices in &lt;code&gt;/dev&lt;/code&gt;. &lt;code&gt;mdev&lt;/code&gt; reads &lt;code&gt;/etc/mdev.conf&lt;/code&gt;
and according to &lt;a href=&quot;https://git.busybox.net/busybox/plain/docs/mdev.txt&quot;&gt;mdev documentation&lt;/a&gt; one
can define a command to be executed per device definition.
The command which is going to be used to change network interface name is &lt;code&gt;nameif&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;%2Fetc%2Fmdev.conf+configuration&quot; name=&quot;%2Fetc%2Fmdev.conf+configuration&quot;&gt;&lt;code&gt;/etc/mdev.conf&lt;/code&gt; configuration&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;-SUBSYSTEM=net;DEVPATH=.*/net/.*;.*     root:root 600 @/sbin/nameif -s&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here we tell &lt;code&gt;mdev&lt;/code&gt; to call &lt;code&gt;nameif&lt;/code&gt; for devices found in &lt;code&gt;/sys/class/net/&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# ls -d -C -1 /sys/class/net/eth*
/sys/class/net/eth1
/sys/class/net/eth2
/sys/class/net/eth3
/sys/class/net/eth4
/sys/class/net/eth5&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;nameif+configuration&quot; name=&quot;nameif+configuration&quot;&gt;&lt;code&gt;nameif&lt;/code&gt; configuration&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;nameif&lt;/code&gt; itself reads &lt;code&gt;/etc/mactab&#039;&lt;/code&gt; by default. Example line for a network interface with
following hwaddr&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# cat /sys/class/net/eth0/address
90:e2:ba:04:28:c0&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;would be&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# grep 90:e2:ba:04:28:c0 /etc/mactab 
dmz0 90:e2:ba:04:28:c0&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;finalization&quot; name=&quot;finalization&quot;&gt;finalization&lt;/h3&gt;
&lt;p&gt;To use renamed network interface without reboot, just call &lt;code&gt;nameif&lt;/code&gt; while the network
interface is down.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# nameif -s&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And finally reboot...&lt;/p&gt;</content>
</entry>
<entry>
<title>Alpine boot menu</title>
<link href="https://www.0ink.net/posts/2023/2023-11-01-alpine-boot.html"></link>
<id>urn:uuid:dbee9804-7406-df13-2f32-c87c26842068</id>
<updated>2023-06-03T00:00:00+02:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This article is an update to my
Alpine Boot Switcher article.

Contents:

Preparing boot device
...]]></summary>
<content type="html">&lt;p&gt;This article is an update to my
&lt;a href=&quot;/posts/2020/2020-10-04-alpine-boot-switcher.html&quot;&gt;Alpine Boot Switcher&lt;/a&gt; article.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Contents:&lt;/p&gt;
&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Preparing+boot+device&quot;&gt;Preparing boot device&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Booting+the+system&quot;&gt;Booting the system&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Adding+a+new+kernel&quot;&gt;Adding a new kernel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#After+a+succesful+reboot&quot;&gt;After a succesful reboot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Alternative+workflow&quot;&gt;Alternative workflow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Removing+installed+kernels&quot;&gt;Removing installed kernels&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Downloads&quot;&gt;Downloads&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;The weakness of that approach was that you needed to be on a running system
to select the active kernel.  So, if you switched to a broken kernel then
you wouldn&#039;t be able to revert back without having to get the media device out
and modifying the file system to switch to a different kernel.&lt;/p&gt;
&lt;p&gt;The approach described here makes use of syslinux or grub menu system to allow you to
select a new kernel.  It requires modifying the &lt;code&gt;init&lt;/code&gt; script in the
&lt;code&gt;initramfs&lt;/code&gt; image so that you can specifically select the right APK boot repository
otherwise it will use the first one found in the boot file system.&lt;/p&gt;
&lt;p&gt;This solution is suitable for physical and virtualized systems using BIOS and UEFI
boot methods.&lt;/p&gt;
&lt;p&gt;The high level approach is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;prepare a bootable USB or virtual disk image.&lt;/li&gt;
&lt;li&gt;during system operation, use the &lt;code&gt;inst_iso&lt;/code&gt; script to install a new
Alpine release.&lt;/li&gt;
&lt;li&gt;You can use the &lt;code&gt;mkmenu&lt;/code&gt; script to select the new kernel or ...&lt;/li&gt;
&lt;li&gt;Re-boot the system, syslinux (on BIOS systems) or grub (on UEFI) systems
will show a menu to let you select the boot kernel or boot a default
kernel after 10 seconds (unless configured for a different time-out).&lt;/li&gt;
&lt;li&gt;If the boot was succesful, you can use &lt;code&gt;mkmenu&lt;/code&gt; to make the current
kernel the default kernel.&lt;/li&gt;
&lt;li&gt;If the boot fails, then you can hard boot the system and use the boot
menu to select a known working kernel.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Preparing+boot+device&quot; name=&quot;Preparing+boot+device&quot;&gt;Preparing boot device&lt;/h2&gt;
&lt;p&gt;Use the &lt;code&gt;mkuub&lt;/code&gt; script to create a bootable USB drive or a bootable disk image.&lt;/p&gt;
&lt;p&gt;The USB drive can be used
on a physical system and supports UEFI and BIOS boot methods (BIOS boot is untested).  &lt;/p&gt;
&lt;p&gt;The bootable disk image is meant to be used on a virtualized environment.  I have tested
it on &lt;code&gt;virsh on kvm&lt;/code&gt; and uses the BIOS boot method.  It has UEFI boot support files but
I have not tested this.&lt;/p&gt;
&lt;p&gt;Usage:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    ./mkuusb.sh [options] isofile [usbdev]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--serial&lt;/code&gt; : Enable serial console&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--ovl=ovlfile&lt;/code&gt; : overlay file to use&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--boot-label=label&lt;/code&gt; : boot partition label &lt;br /&gt;Defaults to a random label unless ovl is specifed.
In that case, it will take the label from the filesystem mounted as
&lt;code&gt;/media/boot&lt;/code&gt; from &lt;code&gt;/etc/fstab&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--boot-size=size&lt;/code&gt; : boot partition size &lt;br /&gt;If not specified it will default to the entire drive
or up to half the drive (if data partition is enabled)
up to 8GB.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--data&lt;/code&gt; : create a data partition&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--data-label=label&lt;/code&gt; : label for data partition_disc &lt;br /&gt;Defaults to a random label unless ovl is specifed
then will take the label from the filesystem mounted as
&lt;code&gt;/media/data&lt;/code&gt; from &lt;code&gt;/etc/fstab&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--data-size=size&lt;/code&gt; : data partition size &lt;br /&gt;size defaults to the remaining of the disk&lt;/li&gt;
&lt;li&gt;&lt;code&gt;isofile&lt;/code&gt;: ISO file to use as the base alpine install&lt;/li&gt;
&lt;li&gt;&lt;code&gt;usbhdd&lt;/code&gt; : &lt;code&gt;/dev/path&lt;/code&gt; to the thumb drive that will be installed. &lt;br /&gt;It will try to use a suitable default by looking an unused drive from
the currently connected drives on the system.  Otherwise a target
image file can be specified using:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;img:path/to/image/file[,size]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;if &lt;code&gt;--serial&lt;/code&gt; was used, the boot menu will be configure to use the first
serial port (Usually &lt;code&gt;COM1&lt;/code&gt;) with a speed of 115200.  So you can use
a serial console or the normal display to select the desired kernel.&lt;/p&gt;
&lt;p&gt;When configured using &lt;code&gt;serial&lt;/code&gt;, kernel boot message would be displayed on
the serial console.&lt;/p&gt;
&lt;h2 id=&quot;Booting+the+system&quot; name=&quot;Booting+the+system&quot;&gt;Booting the system&lt;/h2&gt;
&lt;p&gt;After creating the boot device, you can connected to a physical server or
in the case of a bootable image add it to a virtual machine configuration.&lt;/p&gt;
&lt;p&gt;Booting the system will show a boot menu letting you select a kernel.  If
nothing is selected after the time-out period, a configured default
will be used.&lt;/p&gt;
&lt;h2 id=&quot;Adding+a+new+kernel&quot; name=&quot;Adding+a+new+kernel&quot;&gt;Adding a new kernel&lt;/h2&gt;
&lt;p&gt;During normal operation, you may want to upgrade to a new kernel.  To do this
you can use the script:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$bootmedia/scripts/inst_iso.sh&lt;/code&gt; &lt;em&gt;file-or-url&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can pass a iso file name or the URL of an ISO over the network.  If using
an URL, the script will download the image first.&lt;/p&gt;
&lt;p&gt;This will prepare the environment to include the new kernel and add a menu
entry to boot the new kernel.  The default kernel remains as the currently
running kernel.&lt;/p&gt;
&lt;p&gt;At this point, the system can be rebooted. You can then use the
boot menu to select the latest kernel manually.&lt;/p&gt;
&lt;p&gt;If the new kernel fails to boot succesfully, then you may need to hard boot
the system.&lt;/p&gt;
&lt;p&gt;Since the default kernel was not changed, it should boot to the last working
kernel and proceed normally.&lt;/p&gt;
&lt;h2 id=&quot;After+a+succesful+reboot&quot; name=&quot;After+a+succesful+reboot&quot;&gt;After a succesful reboot&lt;/h2&gt;
&lt;p&gt;If the new kernel was succesfully booted, you can use the command:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$bootmedia/mkmenu.sh&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;to make the currently running kernel the default.&lt;/p&gt;
&lt;h2 id=&quot;Alternative+workflow&quot; name=&quot;Alternative+workflow&quot;&gt;Alternative workflow&lt;/h2&gt;
&lt;p&gt;Alternatively, after the new kernel is installed, you can use the command:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$bootmedia/mkmenu.sh --latest&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;to make the new kernel the default one.  You can then re-boot the system
and if everything goes fine, you are done.&lt;/p&gt;
&lt;p&gt;If the system fails to boot then you probably will need to do a hard boot of
the system and use the boot menu to select a working Alpine Linux kernel.&lt;/p&gt;
&lt;p&gt;After a succesful boot, then you can use the command:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$bootmedia/mkmenu.sh&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To make the current running kernel the default.&lt;/p&gt;
&lt;h2 id=&quot;Removing+installed+kernels&quot; name=&quot;Removing+installed+kernels&quot;&gt;Removing installed kernels&lt;/h2&gt;
&lt;p&gt;Alpine Linux boot images are installed in their own folders.  If you want
to remove a kernel, just remove the folder that you want to un-install and
run &lt;code&gt;mkemnu.sh&lt;/code&gt; again to update the boot menu.&lt;/p&gt;
&lt;h2 id=&quot;Downloads&quot; name=&quot;Downloads&quot;&gt;Downloads&lt;/h2&gt;
&lt;p&gt;The code can be found in my repository:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/TortugaLabs/mab/tree/main/uub&quot;&gt;github&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Alpine Linux boot images:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://alpinelinux.org/downloads/&quot;&gt;Alpine Linux ISO downloads&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Adding static routes in alpine linux</title>
<link href="https://www.0ink.net/posts/2023/2023-10-15-alpine-linux-static-route.html"></link>
<id>urn:uuid:2dcf48e8-03e1-f7ed-8e62-2bcb17484f8f</id>
<updated>2023-06-03T00:00:00+02:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[There are several ways to do this documented
in the alpine linux wiki.
My preferred way is to configure it in /etc/network/interfaces.
For example:
auto eth0
iface eth0 inet static
...]]></summary>
<content type="html">&lt;p&gt;There are several ways to do this documented
in the alpine linux &lt;a href=&quot;https://wiki.alpinelinux.org/wiki/How_to_configure_static_routes&quot;&gt;wiki&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;My preferred way is to configure it in &lt;code&gt;/etc/network/interfaces&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;auto eth0
iface eth0 inet static
       address 192.168.0.1
       netmask 255.255.255.0
       up ip route add 10.14.0.0/16 via 192.168.0.2
       up ip route add 192.168.100.0/23 via 192.168.0.3&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that you can actually add these lines in a &lt;code&gt;dhcp&lt;/code&gt; stanza.&lt;/p&gt;
&lt;p&gt;The benefit of doing this is that those routes are added when that interface
is brought up.&lt;/p&gt;
&lt;p&gt;Also, they are kicked off by the &lt;code&gt;networking&lt;/code&gt; init.d file.&lt;/p&gt;</content>
</entry>
<entry>
<title>New NacoWiki</title>
<link href="https://www.0ink.net/posts/2023/2023-10-01-nacowiki.html"></link>
<id>urn:uuid:6c53c921-5601-d4ca-8175-73d94791ad6c</id>
<updated>2023-10-01T00:00:00+02:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Release new version (3.2.1) of NacoWiki.
The following changes are included:

Added document properties
Added opts.yaml
API improvements
...]]></summary>
<content type="html">&lt;p&gt;Release new version (3.2.1) of &lt;a href=&quot;https://github.com/iliu-net/NacoWiki&quot;&gt;NacoWiki&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The following changes are included:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Added document properties&lt;/li&gt;
&lt;li&gt;Added &lt;code&gt;opts.yaml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;API improvements&lt;/li&gt;
&lt;li&gt;Bug fixes and UI improvements&lt;/li&gt;
&lt;li&gt;Additional Plugins:
&lt;ul&gt;
&lt;li&gt;Versions&lt;/li&gt;
&lt;li&gt;AutoTag&lt;/li&gt;
&lt;li&gt;Albatros : Blog site generator (similar to &lt;a href=&quot;https://getpelican.com/&quot;&gt;Pelican&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Most of the changes are to support the new plugins.&lt;/p&gt;
&lt;h2 id=&quot;Versions&quot; name=&quot;Versions&quot;&gt;Versions&lt;/h2&gt;
&lt;p&gt;Now, &lt;a href=&quot;https://github.com/iliu-net/NacoWiki&quot;&gt;NacoWiki&lt;/a&gt; keeps track of previous versions of articles.
This can be disabled on a per directory basis by creating an
&lt;code&gt;opts.yaml&lt;/code&gt; and adding the &lt;code&gt;disable-props: true&lt;/code&gt; entry.&lt;/p&gt;
&lt;h2 id=&quot;AutoTag&quot; name=&quot;AutoTag&quot;&gt;AutoTag&lt;/h2&gt;
&lt;p&gt;When an article is save it will create tags automatically based on
a &lt;code&gt;tagcloud.md&lt;/code&gt; file containing a list of &lt;em&gt;tagging&lt;/em&gt; words.  This
is enabled only if there is a &lt;code&gt;tagcloud.md&lt;/code&gt; file in the sub-directory
or any parent sub-directory.&lt;/p&gt;
&lt;h2 id=&quot;Albatros&quot; name=&quot;Albatros&quot;&gt;Albatros&lt;/h2&gt;
&lt;p&gt;This is a Blog site generator inspired by &lt;a href=&quot;https://getpelican.com/&quot;&gt;Pelican&lt;/a&gt;.  It was
mainly written to migrate this web site from &lt;a href=&quot;https://getpelican.com/&quot;&gt;Pelican&lt;/a&gt; which
uses a Python based markdown implementation to the same markdown
implementation used in &lt;a href=&quot;https://github.com/iliu-net/NacoWiki&quot;&gt;NacoWiki&lt;/a&gt;.  The reason being that
I use &lt;a href=&quot;https://github.com/iliu-net/NacoWiki&quot;&gt;NacoWiki&lt;/a&gt; to edit this Blog, so it makes sense to use
the same code to preview and generate the static web site.&lt;/p&gt;
&lt;p&gt;It uses a slightly modified version of the template I was using
for this website, so the change should be transparent to most
peope.&lt;/p&gt;</content>
</entry>
<entry>
<title>Migrated to NacoWiki Albatros</title>
<link href="https://www.0ink.net/posts/2023/2023-10-01-migrated.html"></link>
<id>urn:uuid:d93e2b36-16a3-47f0-50b6-1a16f1f6adff</id>
<updated>2023-09-29T00:00:00+02:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This edition marks the migration of my Blog from
Pelican to NacoWiki Albatros.
This is a mostly transparent change.  I wrote Albatros specifically
to migrate this web site from Pelican to a php based markdown
implementation.  As such, it uses a slightly modified version
of the Pelican theme.
...]]></summary>
<content type="html">&lt;p&gt;This edition marks the migration of my Blog from
&lt;a href=&quot;https://getpelican.com/&quot;&gt;Pelican&lt;/a&gt; to &lt;a href=&quot;https://github.com/iliu-net/nanowiki&quot;&gt;NacoWiki&lt;/a&gt; Albatros.&lt;/p&gt;
&lt;p&gt;This is a mostly transparent change.  I wrote Albatros specifically
to migrate this web site from &lt;a href=&quot;https://getpelican.com/&quot;&gt;Pelican&lt;/a&gt; to a php based markdown
implementation.  As such, it uses a slightly modified version
of the &lt;a href=&quot;https://getpelican.com/&quot;&gt;Pelican&lt;/a&gt; theme.&lt;/p&gt;
&lt;p&gt;The main changes from the previous site are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Re-arranged file structure&lt;/li&gt;
&lt;li&gt;Built-in search functionality.  This no longer depends on duck-duck-go.&lt;/li&gt;
&lt;li&gt;Uses the same renderer as &lt;a href=&quot;https://github.com/iliu-net/nanowiki&quot;&gt;NacoWiki&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>File system encryption in Alpine Linux</title>
<link href="https://www.0ink.net/posts/2023/2023-09-15-encrypting-fs-in-alpine.html"></link>
<id>urn:uuid:e63b4887-cd01-9846-0e64-009f94fb8012</id>
<updated>2024-11-07T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[This is similar to my previous article
Encrypting Filesystem in Void Linux
but for Alpine Linux
The point of this recipe is to create a encrypted file sytem
so that when the disc is disposed, it does not need to be
securely erased.  This is particularly important for SSD devices
...]]></summary>
<content type="html">&lt;p&gt;This is similar to my previous article
&lt;a href=&quot;/posts/2019/2019-02-28-encrypting-fs-in-void.html&quot;&gt;Encrypting Filesystem in Void Linux&lt;/a&gt;
but for Alpine Linux&lt;/p&gt;
&lt;p&gt;The point of this recipe is to create a encrypted file sytem
so that when the disc is disposed, it does not need to be
securely erased.  This is particularly important for SSD devices
since because of block remapping (for wear levelling) data can&#039;t
be overwritten consistently.&lt;/p&gt;
&lt;p&gt;The idea is that the boot/root filesystem containing the encryption
keys are stored in a different device as the encrypted file system.&lt;/p&gt;
&lt;h2 id=&quot;Create+block+devices&quot; name=&quot;Create+block+devices&quot;&gt;Create block devices&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;apk add lvm2 cryptsetup

dd if=/dev/urandom of=/etc/crypto_keyfile.bin bs=1024 count=4
chmod 000 /etc/crypto_keyfile.bin
cryptsetup luksFormat /dev/xda2 /etc/crypto_keyfile.bin
cryptsetup luksOpen --key-file=/etc/crypto_keyfile.bin /dev/xda2 crypt-pool
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Look up the UUID&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;blkid /dev/xda2&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Edit &lt;code&gt;dmcrypt&lt;/code&gt; service configuration in &lt;code&gt;/etc/conf.d/dmcrypt&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;target=crypt-pool
source=&quot;UUID=xxxxxxxxxxxxxxxxxxxxxxxxxxxx&quot;
key=/etc/crypto_keyfile.bin
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And enable it with this command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rc-update add dmcrypt boot
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point it would be good to backup:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/etc/conf.d/dmcrypt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/etc/crypto_keyfile.bin&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Reboot and make sure that the block device gets created on start-up.&lt;/p&gt;
&lt;p&gt;Add to LVM:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;vgcreate pool /dev/mapper/crypt-pool
lvcreate --name home0 -L 20G pool
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create your file-system and add it to &lt;code&gt;/etc/fstab&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Alpine Linux uses the same encryption infrastructure as gentoo,
so the &lt;a href=&quot;https://wiki.gentoo.org/wiki/Dm-crypt&quot;&gt;gentoo instructions&lt;/a&gt; apply here.&lt;/p&gt;</content>
</entry>
<entry>
<title>CSS Tips</title>
<link href="https://www.0ink.net/posts/2023/2023-09-01-css-tips.html"></link>
<id>urn:uuid:df3cf520-8b59-7460-4951-523b0b2e583e</id>
<updated>2023-05-18T00:00:00+02:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[
Style editor
Sending query string with PHP
Position sticky
Flexbox and Grid

...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Style+editor&quot;&gt;Style editor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Sending+query+string+with+PHP&quot;&gt;Sending query string with PHP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Position+sticky&quot;&gt;Position sticky&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Flexbox+and+Grid&quot;&gt;Flexbox and Grid&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;Personally, I dislike CSS.  I find it difficult to use because of the lacking
documentation and the fact that different Browsers implement things subtly
different.  Unfortunately, it is something that one has to use today
in order to create funtioning web sites, and things are getting
better all the time.&lt;/p&gt;
&lt;p&gt;Some things that I have learned over time:&lt;/p&gt;
&lt;h2 id=&quot;Style+editor&quot; name=&quot;Style+editor&quot;&gt;Style editor&lt;/h2&gt;
&lt;p&gt;A feature of Firefox, let&#039;s me edit and try CSS styles on the fly.  I find it very
conventient.&lt;/p&gt;
&lt;p&gt;To access it:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open the Firefox menu&lt;/li&gt;
&lt;li&gt;More tools&lt;/li&gt;
&lt;li&gt;Developer tools&lt;/li&gt;
&lt;li&gt;Click on the &lt;code&gt;Style Editor&lt;/code&gt; tab.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In addition, Firefox comes with a &amp;quot;Responsive Design&amp;quot; mode that let&#039;s you simulate the
form factor of different devices.  Granted, you can do this by just resizing the window
but this is very convenient.&lt;/p&gt;
&lt;h2 id=&quot;Sending+query+string+with+PHP&quot; name=&quot;Sending+query+string+with+PHP&quot;&gt;Sending query string with PHP&lt;/h2&gt;
&lt;p&gt;Normally, web browsers would cache JS and CSS files for performance.  For normal
usage, this is the best approach.  For developing, this is a bit inconveneint
as you may be debugging problems that may have been fixed, but the web browser
is still using buggy versions of resource files.&lt;/p&gt;
&lt;p&gt;To get around this, you can code your URLs to include a query string so that
you can change the query string every time you make changes to resources.&lt;/p&gt;
&lt;p&gt;In PHP, what I do, is add to the resource URL, a query string containing the
modification time of the file.  That way, when I update a resource file, it
the query string changes automatically, so the cache expires and renders the
latest version.&lt;/p&gt;
&lt;h2 id=&quot;Position+sticky&quot; name=&quot;Position+sticky&quot;&gt;Position sticky&lt;/h2&gt;
&lt;p&gt;Very often you would like to have a header part of the page to be
always displayed.  I create this effect with the following  CSS:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;css-selector {
  position: sticky;
  top: 0;
  z-index: 100;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The important setting is the &lt;code&gt;posistion: sticky&lt;/code&gt; which makes that element stick
to the window.  The &lt;code&gt;top: 0&lt;/code&gt; positions the element to the top of the page.
Finally, the &lt;code&gt;z-index: 100&lt;/code&gt; makes it so that it will be rendered above other elements
rather than being obscured by them.&lt;/p&gt;
&lt;h2 id=&quot;Flexbox+and+Grid&quot; name=&quot;Flexbox+and+Grid&quot;&gt;Flexbox and Grid&lt;/h2&gt;
&lt;p&gt;These are layout managers that can be used for arranging the page.  For
navigation bars, I prefer to use flexbox.  Whereas for table like layouts, Grid is
usually a good option.&lt;/p&gt;
&lt;p&gt;You should consider using grid layout when: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You have a complex design to work with and want maintainable web pages&lt;/li&gt;
&lt;li&gt;You want to add gaps over the block elements&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You should consider using flexbox when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You have a small design to work with a few rows and columns&lt;/li&gt;
&lt;li&gt;You need to align the element&lt;/li&gt;
&lt;li&gt;You don’t know how your content will look on the page, and you want everything to fit in.&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Local Startup</title>
<link href="https://www.0ink.net/posts/2023/2023-08-15-local-startup.html"></link>
<id>urn:uuid:b01d0c3c-8d43-5093-b618-ab59cd25b545</id>
<updated>2024-02-02T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This is a method to control start-up of applications in a Linux Desktop session
that are run by a local default configuration, but can also be overriden by the user.
This is unlike the /etc/xdg/autostart which is mostly under the control of the
distro packager.
Aslo unlike the /etc/X11/profile.d directory, this runs inside the Desktop Session.
/etc/X11/profile.d gets started before the Desktop session is available.
...]]></summary>
<content type="html">&lt;p&gt;This is a method to control start-up of applications in a Linux Desktop session
that are run by a local default configuration, but can also be overriden by the user.&lt;/p&gt;
&lt;p&gt;This is unlike the &lt;code&gt;/etc/xdg/autostart&lt;/code&gt; which is mostly under the control of the
distro packager.&lt;/p&gt;
&lt;p&gt;Aslo unlike the &lt;code&gt;/etc/X11/profile.d&lt;/code&gt; directory, this runs inside the Desktop Session.
&lt;code&gt;/etc/X11/profile.d&lt;/code&gt; gets started &lt;em&gt;before&lt;/em&gt; the Desktop session is available.&lt;/p&gt;
&lt;p&gt;There is a configuration &lt;code&gt;/etc/xdg/local-startup.cfg&lt;/code&gt;, which contains the local
configuration.  It is a text file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# comments start with #
#
delay 3000 # Number of milliseconds to wait before starting applications
run-cmd /etc/xdg/local-startup.run # Run script
application1.desktop  enable # Enable the application
application2.dekstop  disable # disable this application
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Applications that can be auto started are defined either in &lt;code&gt;/usr/share/applications&lt;/code&gt;
or in &lt;code&gt;$HOME/.local/share/applications&lt;/code&gt; as &lt;code&gt;.desktop&lt;/code&gt; files.&lt;/p&gt;
&lt;p&gt;Adding a &lt;em&gt;enabled&lt;/em&gt; application global config, will start the application by default.&lt;/p&gt;
&lt;p&gt;Adding a &lt;em&gt;disabled&lt;/em&gt; application to the global config, will &lt;em&gt;not&lt;/em&gt; start the application
by default, but it makes it possible for the user to override this setting
in their &lt;code&gt;$HOME/.config/local-startup.cfg&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;Files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/snippets/2023/local-startup/local-startup.tk&quot;&gt;local-startup.tk&lt;/a&gt;
: main implementation script&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/snippets/2023/local-startup/local.cfg&quot;&gt;local.cfg&lt;/a&gt; :
example configuration file&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/snippets/2023/local-startup/local-startup-autostart.desktop&quot;&gt;local-startup-autostart.desktop&lt;/a&gt; :
desktop file to run when session starts&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/snippets/2023/local-startup/local-startup-prefs.desktop&quot;&gt;local-startup-prefs.desktop&lt;/a&gt;
: desktop file for preferences editor&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/snippets/2023/local-startup/local-startup.run&quot;&gt;local-startup.run&lt;/a&gt; :
example run file.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;local-startup.run&quot; name=&quot;local-startup.run&quot;&gt;local-startup.run&lt;/h2&gt;
&lt;p&gt;This is an arbitrary script that can be run on boot-up after the desktop
environment is loaded.  It is meant to run some scripts that can be used
to tweak the User Experiance.  Currently it does:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Closes the pidgin window (that always open regardless)&lt;/li&gt;
&lt;li&gt;Monitors windows that have the size of a maximized window but are not
maximized. These windows still have window re-size grabbers which I
personally find annoying.&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Pulse Audio control in python</title>
<link href="https://www.0ink.net/posts/2023/2023-08-01-pulseaudio-py.html"></link>
<id>urn:uuid:7e2ce5e5-ceeb-0ba8-5eb7-a62eb13d37c8</id>
<updated>2024-02-02T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[I have been using a shell script to toggle pulse audio sinks for some time.  It worked well enough for
switching output among several profiles on a single audio card.  I recently upgraded
my set-up to new hardware.  This hardware for some reason, reported the analog stereo output and
the digital HDMI output as different sound cards.  So my patoggle script did not work well enough
anymore.
Since parsing the output of the pacmd in shell script was becoming a pain, I decided to re-write
...]]></summary>
<content type="html">&lt;p&gt;I have been using a &lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/snippets/2020/pa-hints/patoggle&quot;&gt;shell script&lt;/a&gt; to toggle pulse audio sinks for some time.  It worked well enough for
switching output among several profiles on a single audio card.  I recently upgraded
my set-up to new hardware.  This hardware for some reason, reported the analog stereo output and
the digital HDMI output as different sound cards.  So my patoggle script did not work well enough
anymore.&lt;/p&gt;
&lt;p&gt;Since parsing the output of the &lt;code&gt;pacmd&lt;/code&gt; in shell script was becoming a pain, I decided to re-write
the toggling script in &lt;code&gt;python&lt;/code&gt;.  The new script is &lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/snippets/2020/pa-hints/patoggle.py&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This script depends of two packages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://pypi.org/project/pulsectl/&quot;&gt;pulsectl&lt;/a&gt; : used to control pulse audio&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.python.org/3/library/tkinter.html&quot;&gt;tkinter&lt;/a&gt; : used to show info on screen&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It can be used to control volume and switch audio output.&lt;/p&gt;</content>
</entry>
<entry>
<title>Global hotkeys</title>
<link href="https://www.0ink.net/posts/2023/2023-07-15-global-hk.html"></link>
<id>urn:uuid:2046ad26-87b3-8de9-d2d9-f63006409844</id>
<updated>2024-02-02T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[To make it easier to switch desktop environment I am using
a Desktop Environment independant hot keys configuration using
xbindkeys.  This lets me use the same
keybindings on different Window managers and Desktop Environments.
This code can be found in github.
Included are the follwoing:
...]]></summary>
<content type="html">&lt;p&gt;To make it easier to switch desktop environment I am using
a Desktop Environment independant hot keys configuration using
&lt;a href=&quot;https://www.nongnu.org/xbindkeys/&quot;&gt;xbindkeys&lt;/a&gt;.  This lets me use the same
keybindings on different Window managers and Desktop Environments.&lt;/p&gt;
&lt;p&gt;This code can be found in &lt;a href=&quot;https://github.com/alejandroliu/0ink.net/tree/main/snippets/2023/global-hotkeys&quot;&gt;github&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Included are the follwoing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;hk_helper : a bash or a tcl/tk implementations.  The latest
version is based on tcl/tk.&lt;/li&gt;
&lt;li&gt;xbindkeysrc : hotkey configuration file&lt;/li&gt;
&lt;li&gt;xbindkeys.desktop : &lt;code&gt;/etc/xdg/startup&lt;/code&gt; file.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;obsolete&lt;/em&gt; profile.sh : &lt;code&gt;/etc/X11/profiles.d&lt;/code&gt; file (not used).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The script starts from &lt;code&gt;/etc/xdg/startup&lt;/code&gt;, so as to make sure to
let the desktop environment work and grab as many keys as possible.&lt;/p&gt;
&lt;p&gt;Afterwards, if there is a &lt;code&gt;$HOME/.xbindkeysrc&lt;/code&gt; it will start it first.
This is to allow home user keys to work.&lt;/p&gt;
&lt;p&gt;Finally it will use the global &lt;code&gt;xbindkeysrc&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;Defined hotkeys can be seen in the &lt;code&gt;xbindkeysrc&lt;/code&gt; config file here:&lt;/p&gt;
&lt;script src=&quot;https://tortugalabs.github.io/embed-like-gist/embed.js?style=github&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showCopy=on&amp;amp;fetchFromJsDelivr=on&amp;amp;target=https://github.com/alejandroliu/0ink.net/blob/main/snippets/2023/global-hotkeys/xbindkeysrc&quot;&gt;&lt;/script&gt;</content>
</entry>
<entry>
<title>XPrinterMgr</title>
<link href="https://www.0ink.net/posts/2023/2023-07-01-xprtmgr.html"></link>
<id>urn:uuid:7910744e-e86c-46d6-e3e7-6ad01c6c11c6</id>
<updated>2024-02-02T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This is a small utility to manage a local printer(s) in
a home office setting.
It will show the state of the printer:

accepting or rejecting jobs
enable or disabled printing
...]]></summary>
<content type="html">&lt;p&gt;This is a small utility to manage a local printer(s) in
a home office setting.&lt;/p&gt;
&lt;p&gt;It will show the state of the printer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;accepting or rejecting jobs&lt;/li&gt;
&lt;li&gt;enable or disabled printing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And if &lt;code&gt;sudo&lt;/code&gt; is configured correctly would allow users to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;enable or disable printing&lt;/li&gt;
&lt;li&gt;accept or reject new print jobs&lt;/li&gt;
&lt;li&gt;print a test page&lt;/li&gt;
&lt;li&gt;Show device info.  It would try to start &lt;code&gt;hp-toolbox&lt;/code&gt; if relevant.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It also let&#039;s you manage the local printer job queue.  Allowing you to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;show what jobs are in the queue&lt;/li&gt;
&lt;li&gt;cancel a job&lt;/li&gt;
&lt;li&gt;cancel all job&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It is not intended to replace the CUPS WebUI.  It is just a simple
gui to handle the most common cases for a home office allowing
end-users some simple control of printer management functions.&lt;/p&gt;
&lt;p&gt;It makes use of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/OpenPrinting/pycups&quot;&gt;pycups&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.python.org/3/library/tkinter.html&quot;&gt;python tkinter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/gnikit/tkinter-tooltip&quot;&gt;tkinter-tooltip&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://icons8.com/&quot;&gt;icons8&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The code is in &lt;a href=&quot;https://github.com/alejandroliu/0ink.net/tree/main/snippets/2023/xprtmgr&quot;&gt;github&lt;/a&gt;.&lt;/p&gt;</content>
</entry>
<entry>
<title>rsync filter rules</title>
<link href="https://www.0ink.net/posts/2023/2023-06-15-rsync-rules.html"></link>
<id>urn:uuid:cf0e9fd3-2c3a-bdf9-a9e9-8c3905a188ee</id>
<updated>2023-04-25T00:00:00+02:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[
Rules
Simple include/exclude rules
Simple include/exclude example
Filter rules when deleting
Filter rules in depth
...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Rules&quot;&gt;Rules&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Simple+include%2Fexclude+rules&quot;&gt;Simple include/exclude rules&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Simple+include%2Fexclude+example&quot;&gt;Simple include/exclude example&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Filter+rules+when+deleting&quot;&gt;Filter rules when deleting&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Filter+rules+in+depth&quot;&gt;Filter rules in depth&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Pattern+matching+rules&quot;&gt;Pattern matching rules&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Filter+rule+modifiers&quot;&gt;Filter rule modifiers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Merge-file+filter+rules&quot;&gt;Merge-file filter rules&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#List-clearing+filter+rule&quot;&gt;List-clearing filter rule&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Anchoring+include%2Fexclude+patterns&quot;&gt;Anchoring include/exclude patterns&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Per-directory+rules+and+delete&quot;&gt;Per-directory rules and delete&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;p&gt;The basic options for filtering files in rsync are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--exclude=PATTERN&lt;/code&gt; &lt;br /&gt;This option pecifies an exclude rule.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--exclude-from=FILE&lt;/code&gt;
This option is related to the &lt;code&gt;--exclude&lt;/code&gt; option, but it
specifies a &lt;em&gt;FILE&lt;/em&gt; that contains exclude patterns (one per
line).  Blank lines in the file are ignored, as are whole-
line comments that start with &#039;;&#039; or &#039;#&#039; (filename rules
that contain those characters are unaffected). &lt;br /&gt;If a line consists of just &amp;quot;!&amp;quot;, then the current filter
rules are cleared before adding any further rules. &lt;br /&gt;If FILE is &#039;-&#039;, the list will be read from standard input.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--include=PATTERN&lt;/code&gt; &lt;br /&gt;This option specifies an include rule.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--include-from=FILE&lt;/code&gt;
This option is related to the &lt;code&gt;--include&lt;/code&gt; option, but it
specifies a &lt;em&gt;FILE&lt;/em&gt; that contains include patterns (one per
line).  Blank lines in the file are ignored, as are whole-
line comments that start with &#039;;&#039; or &#039;#&#039; (filename rules
that contain those characters are unaffected).&lt;br /&gt;If a line consists of just &amp;quot;!&amp;quot;, then the current filter
rules are cleared before adding any further rules.&lt;br /&gt;If FILE is &#039;-&#039;, the list will be read from standard input.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--cvs-exclude&lt;/code&gt;, &lt;code&gt;-C&lt;/code&gt;&lt;br /&gt;This is a useful shorthand for excluding a broad range of
files that you often don&#039;t want to transfer between
systems.  It uses a similar algorithm to CVS to determine
if a file should be ignored.&lt;br /&gt;The exclude list is initialized to exclude the following
items (these initial items are marked as perishable:
&lt;pre&gt;&lt;code&gt;RCS SCCS CVS CVS.adm RCSLOG cvslog.*  tags TAGS
.make.state .nse_depinfo *~ #* .#* ,* _$* *$ *.old
*.bak *.BAK *.orig *.rej .del-* *.a *.olb *.o *.obj
*.so *.exe *.Z *.elc *.ln core .svn/ .git/ .hg/ .bzr/&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;then, files listed in a &lt;code&gt;$HOME/.cvsignore&lt;/code&gt; are added to the
list and any files listed in the &lt;code&gt;CVSIGNORE&lt;/code&gt; environment
variable (all cvsignore names are delimited by
whitespace).&lt;br /&gt;Finally, any file is ignored if it is in the same
directory as a &lt;code&gt;.cvsignore&lt;/code&gt; file and matches one of the
patterns listed therein.  Unlike rsync&#039;s filter/exclude
files, these patterns are split on whitespace.&lt;br /&gt;If you&#039;re combining &lt;code&gt;-C&lt;/code&gt; with your own &lt;code&gt;--filter&lt;/code&gt; rules, you
should note that these CVS excludes are appended at the
end of your own rules, regardless of where the &lt;code&gt;-C&lt;/code&gt; was
placed on the command-line.  This makes them a lower
priority than any rules you specified explicitly.  If you
want to control where these CVS excludes get inserted into
your filter rules, you should omit the &lt;code&gt;-C&lt;/code&gt; as a command-
line option and use a combination of &lt;code&gt;--filter=:C&lt;/code&gt; and
&lt;code&gt;--filter=-C&lt;/code&gt; (either on your command-line or by putting the
&lt;code&gt;&quot;:C&quot;&lt;/code&gt; and &lt;code&gt;&quot;-C&quot;&lt;/code&gt; rules into a filter file with your other
rules).  The first option turns on the per-directory
scanning for the &lt;code&gt;.cvsignore&lt;/code&gt; file.  The second option does
a one-time import of the CVS excludes mentioned above.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Rsync supports old-style include/exclude rules and new-style
filter rules.  The older rules are specified using &lt;code&gt;--include&lt;/code&gt; and
&lt;code&gt;--exclude&lt;/code&gt; as well as the &lt;code&gt;--include-from&lt;/code&gt; and &lt;code&gt;--exclude-from&lt;/code&gt;. These
are limited in behavior but they don&#039;t require a &lt;code&gt;&quot;-&quot;&lt;/code&gt; or &lt;code&gt;&quot;+&quot;&lt;/code&gt;
prefix.  An old-style exclude rule is turned into a &lt;code&gt;&quot;- name&quot;&lt;/code&gt;
filter rule (with no modifiers) and an old-style include rule is
turned into a &lt;code&gt;&quot;+ name&quot;&lt;/code&gt; filter rule (with no modifiers).&lt;/p&gt;
&lt;p&gt;For more control on included/excluded files you should use
these options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--filter=RULE&lt;/code&gt;, &lt;code&gt;-f&lt;/code&gt; &lt;br /&gt;This option allows you to add rules to selectively exclude
certain files from the list of files to be transferred.
This is most useful in combination with a recursive
transfer.&lt;br /&gt;You may use as many &lt;code&gt;--filter&lt;/code&gt; options on the command line
as you like to build up the list of files to exclude.  If
the filter contains whitespace, be sure to quote it so
that the shell gives the rule to rsync as a single
argument.  The text below also mentions that you can use
an underscore to replace the space that separates a rule
from its arg.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-F&lt;/code&gt; &lt;br /&gt;The &lt;code&gt;-F&lt;/code&gt; option is a shorthand for adding two &lt;code&gt;--filter&lt;/code&gt; rules
to your command.  The first time it is used is a shorthand
for this rule:
&lt;pre&gt;&lt;code&gt;--filter=&#039;dir-merge /.rsync-filter&#039;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This tells rsync to look for per-directory &lt;code&gt;.rsync-filter&lt;/code&gt;
files that have been sprinkled through the hierarchy and
use their rules to filter the files in the transfer.  If
&lt;code&gt;-F&lt;/code&gt; is repeated, it is a shorthand for this rule:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;--filter=&#039;exclude .rsync-filter&#039;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This filters out the &lt;code&gt;.rsync-filter&lt;/code&gt; files themselves from
the transfer.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Rules&quot; name=&quot;Rules&quot;&gt;Rules&lt;/h2&gt;
&lt;p&gt;The filter rules allow for custom control of several aspects of
how files are handled:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Control which files the sending side puts into the file
list that describes the transfer hierarchy&lt;/li&gt;
&lt;li&gt;Control which files the receiving side protects from
deletion when the file is not in the sender&#039;s file list&lt;/li&gt;
&lt;li&gt;Control which extended attribute names are skipped when
copying xattrs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The rules are either directly specified via option arguments or
they can be read in from one or more files.  The filter-rule
files can even be a part of the hierarchy of files being copied,
affecting different parts of the tree in different ways.&lt;/p&gt;
&lt;h2 id=&quot;Simple+include%2Fexclude+rules&quot; name=&quot;Simple+include%2Fexclude+rules&quot;&gt;Simple include/exclude rules&lt;/h2&gt;
&lt;p&gt;We will first cover the basics of how include &amp;amp; exclude rules
affect what files are transferred, ignoring any deletion side-
effects.  Filter rules mainly affect the contents of directories
that rsync is &amp;quot;recursing&amp;quot; into, but they can also affect a top-level
item in the transfer that was specified as a argument.&lt;/p&gt;
&lt;p&gt;The default for any unmatched file/dir is for it to be included
in the transfer, which puts the file/dir into the sender&#039;s file
list.  The use of an exclude rule causes one or more matching
files/dirs to be left out of the sender&#039;s file list.  An include
rule can be used to limit the effect of an exclude rule that is
matching too many files.&lt;/p&gt;
&lt;p&gt;The order of the rules is important because the first rule that
matches is the one that takes effect.  Thus, if an early rule
excludes a file, no include rule that comes after it can have any
effect. This means that you must place any include overrides
somewhere prior to the exclude that it is intended to limit.&lt;/p&gt;
&lt;p&gt;When a directory is excluded, all its contents and sub-contents
are also excluded.  The sender doesn&#039;t scan through any of it at
all, which can save a lot of time when skipping large unneeded
sub-trees.&lt;/p&gt;
&lt;p&gt;It is also important to understand that the include/exclude rules
are applied to every file and directory that the sender is
recursing into. Thus, if you want a particular deep file to be
included, you have to make sure that none of the directories that
must be traversed on the way down to that file are excluded or
else the file will never be discovered to be included. As an
example, if the directory &amp;quot;a/path&amp;quot; was given as a transfer
argument and you want to ensure that the file
&amp;quot;a/path/down/deep/wanted.txt&amp;quot; is a part of the transfer, then the
sender must not exclude the directories &amp;quot;a/path&amp;quot;, &amp;quot;a/path/down&amp;quot;,
or &amp;quot;a/path/down/deep&amp;quot; as it makes it way scanning through the
file tree.&lt;/p&gt;
&lt;p&gt;When you are working on the rules, it can be helpful to ask rsync
to tell you what is being excluded/included and why.  Specifying
&lt;code&gt;--debug=FILTER&lt;/code&gt; or (when pulling files) &lt;code&gt;-M--debug=FILTER&lt;/code&gt; turns on
level 1 of the &lt;code&gt;FILTER&lt;/code&gt; debug information that will output a
message any time that a file or directory is included or excluded
and which rule it matched.  Beginning in 3.2.4 it will also warn
if a filter rule has trailing whitespace, since an exclude of
&amp;quot;foo &amp;quot; (with a trailing space) will not exclude a file named
&amp;quot;foo&amp;quot;.&lt;/p&gt;
&lt;p&gt;Exclude and include rules can specify wildcard &lt;strong&gt;PATTERN MATCHING RULES&lt;/strong&gt;
(similar to shell wildcards) that allow you to match things
like a file suffix or a portion of a filename.&lt;/p&gt;
&lt;p&gt;A rule can be limited to only affecting a directory by putting a
trailing slash onto the filename.&lt;/p&gt;
&lt;h2 id=&quot;Simple+include%2Fexclude+example&quot; name=&quot;Simple+include%2Fexclude+example&quot;&gt;Simple include/exclude example&lt;/h2&gt;
&lt;p&gt;With the following file tree created on the sending side:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir x/
touch x/file.txt
mkdir x/y/
touch x/y/file.txt
touch x/y/zzz.txt
mkdir x/z/
touch x/z/file.txt&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then the following rsync command will transfer the file
&lt;code&gt;&quot;x/y/file.txt&quot;&lt;/code&gt; and the directories needed to hold it, resulting
in the path &lt;code&gt;&quot;/tmp/x/y/file.txt&quot;&lt;/code&gt; existing on the remote host:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rsync -ai -f&#039;+ x/&#039; -f&#039;+ x/y/&#039; -f&#039;+ x/y/file.txt&#039; -f&#039;- *&#039; x host:/tmp/&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Aside: this copy could also have been accomplished using the &lt;code&gt;-R&lt;/code&gt;
option (though the two commands behave differently if deletions are
enabled):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rsync -aiR x/y/file.txt host:/tmp/&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The following command does not need an include of the &lt;code&gt;&quot;x&quot;&lt;/code&gt;
directory because it is not a part of the transfer (note the
traililng slash).  Running this command would copy just
&lt;code&gt;&quot;/tmp/x/file.txt&quot;&lt;/code&gt; because the &lt;code&gt;&quot;y&quot;&lt;/code&gt; and &lt;code&gt;&quot;z&quot;&lt;/code&gt; dirs get excluded:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rsync -ai -f&#039;+ file.txt&#039; -f&#039;- *&#039; x/ host:/tmp/x/&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This command would omit the &lt;code&gt;zzz.txt&lt;/code&gt; file while copying &lt;code&gt;&quot;x&quot;&lt;/code&gt; and
everything else it contains:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rsync -ai -f&#039;- zzz.txt&#039; x host:/tmp/&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Filter+rules+when+deleting&quot; name=&quot;Filter+rules+when+deleting&quot;&gt;Filter rules when deleting&lt;/h2&gt;
&lt;p&gt;By default the include &amp;amp; exclude filter rules affect both the
sender (as it creates its file list) and the receiver (as it
creates its file lists for calculating deletions).  If no delete
option is in effect, the receiver skips creating the delete-
related file lists.  This two-sided default can be manually
overridden so that you are only specifying sender rules or
receiver rules, as described in the
&lt;strong&gt;FILTER RULES IN DEPTH&lt;/strong&gt;
section.&lt;/p&gt;
&lt;p&gt;When deleting, an exclude protects a file from being removed on
the receiving side while an include overrides that protection
(putting the file at risk of deletion). The default is for a file
to be at risk -- its safety depends on it matching a
corresponding file from the sender.&lt;/p&gt;
&lt;p&gt;An example of the two-sided exclude effect can be illustrated by
the copying of a C development directory between two systems.  When
doing a touch-up copy, you might want to skip copying the built
executable and the .o files (sender hide) so that the receiving
side can build their own and not lose any object files that are
already correct (receiver protect).  For instance:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rsync -ai --del -f&#039;- *.o&#039; -f&#039;- cmd&#039; src host:/dest/&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that using &lt;code&gt;-f&#039;-p *.o&#039;&lt;/code&gt; is even better than &lt;code&gt;-f&#039;- *.o&#039;&lt;/code&gt; if there
is a chance that the directory structure may have changed.  The
&lt;code&gt;&quot;p&quot;&lt;/code&gt; modifier is discussed in &lt;strong&gt;FILTER RULE MODIFIERS&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;One final note, if your shell doesn&#039;t mind unexpanded wildcards,
you could simplify the typing of the filter options by using an
underscore in place of the space and leaving off the quotes.  For
instance, &lt;code&gt;-f -_*.o -f -_cmd&lt;/code&gt; (and similar) could be used instead
of the filter options above.&lt;/p&gt;
&lt;h2 id=&quot;Filter+rules+in+depth&quot; name=&quot;Filter+rules+in+depth&quot;&gt;Filter rules in depth&lt;/h2&gt;
&lt;p&gt;Rsync builds an ordered list of filter rules as specified on the
command-line and/or read-in from files.  New style filter rules
have the following syntax:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;RULE [PATTERN_OR_FILENAME]
RULE,MODIFIERS [PATTERN_OR_FILENAME]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You have your choice of using either short or long &lt;em&gt;RULE&lt;/em&gt; names, as
described below.  If you use a short-named rule, the &lt;code&gt;&#039;,&#039;&lt;/code&gt;
separating the &lt;em&gt;RULE&lt;/em&gt; from the &lt;em&gt;MODIFIERS&lt;/em&gt; is optional.  The &lt;em&gt;PATTERN&lt;/em&gt;
or &lt;em&gt;FILENAME&lt;/em&gt; that follows (when present) must come after either a
single space or an underscore (_). Any additional spaces and/or
underscores are considered to be a part of the pattern name.
Here are the available rule prefixes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;exclude, &#039;-&#039;&lt;/code&gt; &lt;br /&gt;specifies an exclude pattern that (by default) is both a
hide and a protect.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;include, &#039;+&#039;&lt;/code&gt; &lt;br /&gt;specifies an include pattern that (by default) is both a
show and a risk.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;merge, &#039;.&#039;&lt;/code&gt; &lt;br /&gt;specifies a merge-file on the client side to read for more
rules.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dir-merge, &#039;:&#039;&lt;/code&gt; &lt;br /&gt;specifies a per-directory merge-file.  Using this kind of
filter rule requires that you trust the sending side&#039;s
filter checking, so it has the side-effect mentioned under
the &lt;code&gt;--trust-sender&lt;/code&gt; option.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hide, &#039;H&#039;&lt;/code&gt; &lt;br /&gt;specifies a pattern for hiding files from the transfer.
Equivalent to a sender-only exclude, so &lt;code&gt;-f&#039;H foo&#039;&lt;/code&gt; could
also be specified as &lt;code&gt;-f&#039;-s foo&#039;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;show, &#039;S&#039;&lt;/code&gt; &lt;br /&gt;files that match the pattern are not hidden. Equivalent to
a sender-only include, so &lt;code&gt;-f&#039;S foo&#039;&lt;/code&gt; could also be
specified as &lt;code&gt;-f&#039;+s foo&#039;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;protect, &#039;P&#039;&lt;/code&gt; &lt;br /&gt;specifies a pattern for protecting files from deletion.
Equivalent to a receiver-only exclude, so &lt;code&gt;-f&#039;P foo&#039;&lt;/code&gt; could
also be specified as &lt;code&gt;-f&#039;-r foo&#039;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;risk, &#039;R&#039;&lt;/code&gt; &lt;br /&gt;files that match the pattern are not protected. Equivalent
to a receiver-only include, so &lt;code&gt;-f&#039;R foo&#039;&lt;/code&gt; could also be
specified as &lt;code&gt;-f&#039;+r foo&#039;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;clear, &#039;!&#039;&lt;/code&gt;
clears the current include/exclude list (takes no arg)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When rules are being read from a file (using &lt;code&gt;merge&lt;/code&gt; or &lt;code&gt;dir-merge&lt;/code&gt;),
empty lines are ignored, as are whole-line comments that start
with a &lt;code&gt;&#039;#&#039;&lt;/code&gt; (filename rules that contain a hash character are
unaffected).&lt;/p&gt;
&lt;p&gt;Note also that the &lt;code&gt;--filter&lt;/code&gt;, &lt;code&gt;--include&lt;/code&gt;, and &lt;code&gt;--exclude&lt;/code&gt; options
take one rule/pattern each.  To add multiple ones, you can repeat
the options on the command-line, use the merge-file syntax of the
&lt;code&gt;--filter&lt;/code&gt; option, or the &lt;code&gt;--include-from&lt;/code&gt; / &lt;code&gt;--exclude-from&lt;/code&gt; options.&lt;/p&gt;
&lt;h2 id=&quot;Pattern+matching+rules&quot; name=&quot;Pattern+matching+rules&quot;&gt;Pattern matching rules&lt;/h2&gt;
&lt;p&gt;Most of the rules mentioned above take an argument that specifies
what the rule should match.  If rsync is recursing through a
directory hierarchy, keep in mind that each pattern is matched
against the name of every directory in the descent path as rsync
finds the filenames to send.&lt;/p&gt;
&lt;p&gt;The matching rules for the pattern argument take several forms:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If a pattern contains a &lt;code&gt;/&lt;/code&gt; (not counting a trailing slash)
or a &lt;code&gt;&quot;**&quot;&lt;/code&gt; (which can match a slash), then the pattern is
matched against the full pathname, including any leading
directories within the transfer.  If the pattern doesn&#039;t
contain a (non-trailing) &lt;code&gt;/&lt;/code&gt; or a &lt;code&gt;&quot;**&quot;&lt;/code&gt;, then it is matched
only against the final component of the filename or
pathname. For example, &lt;code&gt;foo&lt;/code&gt; means that the final path
component must be &lt;code&gt;&quot;foo&quot;&lt;/code&gt; while &lt;code&gt;foo/bar&lt;/code&gt; would match the last
2 elements of the path (as long as both elements are
within the transfer).&lt;/li&gt;
&lt;li&gt;A pattern that ends with a &lt;code&gt;/&lt;/code&gt; only matches a directory, not
a regular file, symlink, or device.&lt;/li&gt;
&lt;li&gt;A pattern that starts with a &lt;code&gt;/&lt;/code&gt; is anchored to the start of
the transfer path instead of the end.  For example,
/foo/&lt;strong&gt; or /foo/bar/&lt;/strong&gt; match only leading elements in the
path.  If the rule is read from a per-directory filter
file, the transfer path being matched will begin at the
level of the filter file instead of the top of the
transfer.  See the section on
&lt;strong&gt;ANCHORING INCLUDE/EXCLUDE PATTERNS&lt;/strong&gt;
for a full discussion of how to specify a pattern
that matches at the root of the transfer.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Rsync chooses between doing a simple string match and wildcard
matching by checking if the pattern contains one of these three
wildcard characters: &#039;*&#039;, &#039;?&#039;, and &#039;[&#039; :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a &lt;code&gt;&#039;?&#039;&lt;/code&gt; matches any single character except a slash (&lt;code&gt;/&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;&#039;*&#039;&lt;/code&gt; matches zero or more non-slash characters.&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;&#039;**&#039;&lt;/code&gt; matches zero or more characters, including slashes.&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;&#039;[&#039;&lt;/code&gt; introduces a character class, such as &lt;code&gt;[a-z]&lt;/code&gt; or
&lt;code&gt;[[:alpha:]]&lt;/code&gt;, that must match one character.&lt;/li&gt;
&lt;li&gt;a trailing &lt;code&gt;***&lt;/code&gt; in the pattern is a shorthand that allows
you to match a directory and all its contents using a
single rule.  For example, specifying &lt;code&gt;&quot;dir_name/***&quot;&lt;/code&gt; will
match both the &lt;code&gt;&quot;dir_name&quot;&lt;/code&gt; directory (as if &lt;code&gt;&quot;dir_name/&quot;&lt;/code&gt; had
been specified) and everything in the directory (as if
&lt;code&gt;&quot;dir_name/**&quot;&lt;/code&gt; had been specified).&lt;/li&gt;
&lt;li&gt;a backslash can be used to escape a wildcard character,
but it is only interpreted as an escape character if at
least one wildcard character is present in the match
pattern. For instance, the pattern &lt;code&gt;&quot;foo\bar&quot;&lt;/code&gt; matches that
single backslash literally, while the pattern &lt;code&gt;&quot;foo\bar*&quot;&lt;/code&gt;
would need to be changed to &lt;code&gt;&quot;foo\\bar*&quot;&lt;/code&gt; to avoid the &lt;code&gt;&quot;\b&quot;&lt;/code&gt;
becoming just &lt;code&gt;&quot;b&quot;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here are some examples of exclude/include matching:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Option &lt;code&gt;-f&#039;- *.o&#039;&lt;/code&gt; would exclude all filenames ending with
&lt;code&gt;.o&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Option &lt;code&gt;-f&#039;- /foo&#039;&lt;/code&gt; would exclude a file (or directory)
named &lt;code&gt;foo&lt;/code&gt; in the transfer-root directory&lt;/li&gt;
&lt;li&gt;Option &lt;code&gt;-f&#039;- foo/&#039;&lt;/code&gt; would exclude any directory named &lt;code&gt;foo&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Option &lt;code&gt;-f&#039;- foo/*/bar&#039;&lt;/code&gt; would exclude any file/dir named
bar which is at two levels below a directory named &lt;code&gt;foo&lt;/code&gt; (if
&lt;code&gt;foo&lt;/code&gt; is in the transfer)&lt;/li&gt;
&lt;li&gt;Option &lt;code&gt;-f&#039;- /foo/**/bar&#039;&lt;/code&gt; would exclude any file/dir named
&lt;code&gt;bar&lt;/code&gt; that was two or more levels below a top-level
directory named &lt;code&gt;foo&lt;/code&gt; (note that &lt;code&gt;/foo/bar&lt;/code&gt; is not excluded by
this)&lt;/li&gt;
&lt;li&gt;Options &lt;code&gt;-f&#039;+ */&#039;&lt;/code&gt; &lt;code&gt;-f&#039;+ *.c&#039;&lt;/code&gt; &lt;code&gt;-f&#039;- *&#039;&lt;/code&gt; would include all
directories and &lt;code&gt;.c&lt;/code&gt; source files but nothing else&lt;/li&gt;
&lt;li&gt;Options &lt;code&gt;-f&#039;+ foo/&#039;&lt;/code&gt; &lt;code&gt;-f&#039;+ foo/bar.c&#039;&lt;/code&gt; &lt;code&gt;-f&#039;- *&#039;&lt;/code&gt; would include
only the &lt;code&gt;foo&lt;/code&gt; directory and &lt;code&gt;foo/bar.c&lt;/code&gt; (the &lt;code&gt;foo&lt;/code&gt; directory
must be explicitly included or it would be excluded by the
&lt;code&gt;&quot;- *&quot;&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Filter+rule+modifiers&quot; name=&quot;Filter+rule+modifiers&quot;&gt;Filter rule modifiers&lt;/h2&gt;
&lt;p&gt;The following modifiers are accepted after an include &lt;code&gt;(+)&lt;/code&gt; or
exclude &lt;code&gt;(-)&lt;/code&gt; rule:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;/&lt;/code&gt; specifies that the include/exclude rule should be
matched against the absolute pathname of the current item.
For example, &lt;code&gt;-f&#039;-/ /etc/passwd&#039;&lt;/code&gt; would exclude the &lt;code&gt;passwd&lt;/code&gt;
file any time the transfer was sending files from the
&lt;code&gt;&quot;/etc&quot;&lt;/code&gt; directory, and &lt;code&gt;&quot;-/ subdir/foo&quot;&lt;/code&gt; would always exclude
&lt;code&gt;&quot;foo&quot;&lt;/code&gt; when it is in a dir named &lt;code&gt;&quot;subdir&quot;&lt;/code&gt;, even if &lt;code&gt;&quot;foo&quot;&lt;/code&gt; is
at the root of the current transfer.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;!&lt;/code&gt; specifies that the include/exclude should take effect
if the pattern fails to match.  For instance, &lt;code&gt;-f&#039;-! */&#039;&lt;/code&gt;
would exclude all non-directories.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;C&lt;/code&gt; is used to indicate that all the global CVS-exclude
rules should be inserted as excludes in place of the &lt;code&gt;&quot;-C&quot;&lt;/code&gt;.
No arg should follow.&lt;/li&gt;
&lt;li&gt;An &lt;code&gt;s&lt;/code&gt; is used to indicate that the rule applies to the
sending side.  When a rule affects the sending side, it
affects what files are put into the sender&#039;s file list.
The default is for a rule to affect both sides unless
&lt;code&gt;--delete-excluded&lt;/code&gt; was specified, in which case default
rules become sender-side only.  See also the hide &lt;code&gt;(H)&lt;/code&gt; and
show &lt;code&gt;(S)&lt;/code&gt; rules, which are an alternate way to specify
sending-side includes/excludes.&lt;/li&gt;
&lt;li&gt;An &lt;code&gt;r&lt;/code&gt; is used to indicate that the rule applies to the
receiving side.  When a rule affects the receiving side,
it prevents files from being deleted.  See the &lt;code&gt;s&lt;/code&gt; modifier
for more info.  See also the protect &lt;code&gt;(P)&lt;/code&gt; and risk &lt;code&gt;(R)&lt;/code&gt;
rules, which are an alternate way to specify receiver-side
includes/excludes.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;p&lt;/code&gt; indicates that a rule is perishable, meaning that it
is ignored in directories that are being deleted.  For
instance, the &lt;code&gt;--cvs-exclude&lt;/code&gt; &lt;code&gt;(-C)&lt;/code&gt; option&#039;s default rules
that exclude things like &lt;code&gt;&quot;CVS&quot;&lt;/code&gt; and &lt;code&gt;&quot;*.o&quot;&lt;/code&gt; are marked as
perishable, and will not prevent a directory that was
removed on the source from being deleted on the
destination.&lt;/li&gt;
&lt;li&gt;An &lt;code&gt;x&lt;/code&gt; indicates that a rule affects &lt;code&gt;xattr&lt;/code&gt; names in &lt;code&gt;xattr&lt;/code&gt;
copy/delete operations (and is thus ignored when matching
file/dir names).  If no xattr-matching rules are
specified, a default xattr filtering rule is used.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Merge-file+filter+rules&quot; name=&quot;Merge-file+filter+rules&quot;&gt;Merge-file filter rules&lt;/h2&gt;
&lt;p&gt;You can merge whole files into your filter rules by specifying
either a &lt;code&gt;merge&lt;/code&gt; (.) or a &lt;code&gt;dir-merge&lt;/code&gt; (:) filter rule (as introduced
in the &lt;strong&gt;FILTER RULES&lt;/strong&gt; section above).&lt;/p&gt;
&lt;p&gt;There are two kinds of merged files -- single-instance (&#039;.&#039;) and
per-directory (&#039;😂.  A single-instance merge file is read one
time, and its rules are incorporated into the filter list in the
place of the &amp;quot;.&amp;quot; rule.  For per-directory merge files, rsync will
scan every directory that it traverses for the named file,
merging its contents when the file exists into the current list
of inherited rules.  These per-directory rule files must be
created on the sending side because it is the sending side that
is being scanned for the available files to transfer.  These rule
files may also need to be transferred to the receiving side if
you want them to affect what files don&#039;t get deleted (see
&lt;strong&gt;PER-DIRECTORY RULES AND DELETE&lt;/strong&gt;
below).&lt;/p&gt;
&lt;p&gt;Some examples:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;merge /etc/rsync/default.rules
. /etc/rsync/default.rules
dir-merge .per-dir-filter
dir-merge,n- .non-inherited-per-dir-excludes
:n- .non-inherited-per-dir-excludes&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The following modifiers are accepted after a merge or dir-merge
rule:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;-&lt;/code&gt; specifies that the file should consist of only exclude
patterns, with no other rule-parsing except for in-file
comments.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;+&lt;/code&gt; specifies that the file should consist of only include
patterns, with no other rule-parsing except for in-file
comments.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;C&lt;/code&gt; is a way to specify that the file should be read in a
CVS-compatible manner.  This turns on &lt;code&gt;&#039;n&#039;&lt;/code&gt;, &lt;code&gt;&#039;w&#039;&lt;/code&gt;, and &lt;code&gt;&#039;-&#039;&lt;/code&gt;,
but also allows the list-clearing token &lt;code&gt;(!)&lt;/code&gt; to be
specified.  If no filename is provided, &lt;code&gt;&quot;.cvsignore&quot;&lt;/code&gt; is
assumed.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;e&lt;/code&gt; will exclude the merge-file name from the transfer;
e.g.  &lt;code&gt;&quot;dir-merge,e .rules&quot;&lt;/code&gt; is like &lt;code&gt;&quot;dir-merge .rules&quot;&lt;/code&gt; and
&lt;code&gt;&quot;- .rules&quot;.&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;An &lt;code&gt;n&lt;/code&gt; specifies that the rules are not inherited by
subdirectories.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;w&lt;/code&gt; specifies that the rules are word-split on whitespace
instead of the normal line-splitting.  This also turns off
comments.  Note: the space that separates the prefix from
the rule is treated specially, so &lt;code&gt;&quot;- foo + bar&quot;&lt;/code&gt; is parsed
as two rules (assuming that prefix-parsing wasn&#039;t also
disabled).&lt;/li&gt;
&lt;li&gt;You may also specify any of the modifiers for the &lt;code&gt;&quot;+&quot;&lt;/code&gt; or
&lt;code&gt;&quot;-&quot;&lt;/code&gt; rules (above) in order to have the rules that are read
in from the file default to having that modifier set
(except for the &lt;code&gt;!&lt;/code&gt; modifier, which would not be useful).
For instance, &lt;code&gt;&quot;merge,-/ .excl&quot;&lt;/code&gt; would treat the contents of
&lt;code&gt;.excl&lt;/code&gt; as absolute-path excludes, while &lt;code&gt;&quot;dir-merge,s .filt&quot;&lt;/code&gt;
and &lt;code&gt;&quot;:sC&quot;&lt;/code&gt; would each make all their per-directory rules
apply only on the sending side.  If the merge rule
specifies sides to affect (via the &lt;code&gt;s&lt;/code&gt; or &lt;code&gt;r&lt;/code&gt; modifier or
both), then the rules in the file must not specify sides
(via a modifier or a rule prefix such as hide).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Per-directory rules are inherited in all subdirectories of the
directory where the merge-file was found unless the &lt;code&gt;&#039;n&#039;&lt;/code&gt; modifier
was used.  Each subdirectory&#039;s rules are prefixed to the
inherited per-directory rules from its parents, which gives the
newest rules a higher priority than the inherited rules.  The
entire set of dir-merge rules are grouped together in the spot
where the merge-file was specified, so it is possible to override
dir-merge rules via a rule that got specified earlier in the list
of global rules.  When the list-clearing rule &lt;code&gt;(&quot;!&quot;)&lt;/code&gt; is read from
a per-directory file, it only clears the inherited rules for the
current merge file.&lt;/p&gt;
&lt;p&gt;Another way to prevent a single rule from a dir-merge file from
being inherited is to anchor it with a leading slash.  Anchored
rules in a per-directory merge-file are relative to the merge-
file&#039;s directory, so a pattern &lt;code&gt;&quot;/foo&quot;&lt;/code&gt; would only match the file
&lt;code&gt;&quot;foo&quot;&lt;/code&gt; in the directory where the dir-merge filter file was found.&lt;/p&gt;
&lt;p&gt;Here&#039;s an example filter file which you&#039;d specify via
&lt;code&gt;--filter=&quot;. file&quot;&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;merge /home/user/.global-filter
- *.gz
dir-merge .rules
+ *.[ch]
- *.o
- foo*&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will merge the contents of the &lt;code&gt;/home/user/.global-filter&lt;/code&gt;
file at the start of the list and also turns the &lt;code&gt;&quot;.rules&quot;&lt;/code&gt;
filename into a per-directory filter file.  All rules read in
prior to the start of the directory scan follow the global
anchoring rules (i.e. a leading slash matches at the root of the
transfer).&lt;/p&gt;
&lt;p&gt;If a per-directory merge-file is specified with a path that is a
parent directory of the first transfer directory, rsync will scan
all the parent dirs from that starting point to the transfer
directory for the indicated per-directory file.  For instance,
here is a common filter (see -F):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;--filter=&#039;: /.rsync-filter&#039;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That rule tells rsync to scan for the file .rsync-filter in all
directories from the root down through the parent directory of
the transfer prior to the start of the normal directory scan of
the file in the directories that are sent as a part of the
transfer. (Note: for an rsync daemon, the root is always the same
as the module&#039;s &amp;quot;path&amp;quot;.)&lt;/p&gt;
&lt;p&gt;Some examples of this pre-scanning for per-directory files:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rsync -avF /src/path/ /dest/dir
rsync -av --filter=&#039;: ../../.rsync-filter&#039; /src/path/ /dest/dir
rsync -av --filter=&#039;: .rsync-filter&#039; /src/path/ /dest/dir&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first two commands above will look for &lt;code&gt;&quot;.rsync-filter&quot;&lt;/code&gt; in &lt;code&gt;&quot;/&quot;&lt;/code&gt;
and &lt;code&gt;&quot;/src&quot;&lt;/code&gt; before the normal scan begins looking for the file in
&lt;code&gt;&quot;/src/path&quot;&lt;/code&gt; and its subdirectories.  The last command avoids the
parent-dir scan and only looks for the &lt;code&gt;&quot;.rsync-filter&quot;&lt;/code&gt; files in
each directory that is a part of the transfer.&lt;/p&gt;
&lt;p&gt;If you want to include the contents of a &lt;code&gt;&quot;.cvsignore&quot;&lt;/code&gt; in your
patterns, you should use the rule &lt;code&gt;&quot;:C&quot;&lt;/code&gt;, which creates a dir-merge
of the &lt;code&gt;.cvsignore file&lt;/code&gt;, but parsed in a CVS-compatible manner.
You can use this to affect where the &lt;code&gt;--cvs-exclude&lt;/code&gt; &lt;code&gt;(-C)&lt;/code&gt; option&#039;s
inclusion of the per-directory .cvsignore file gets placed into
your rules by putting the &lt;code&gt;&quot;:C&quot;&lt;/code&gt; wherever you like in your filter
rules.  Without this, rsync would add the dir-merge rule for the
&lt;code&gt;.cvsignore&lt;/code&gt; file at the end of all your other rules (giving it a
lower priority than your command-line rules).  For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat &amp;lt;&amp;lt;EOT | rsync -avC --filter=&#039;. -&#039; a/ b
+ foo.o
:C
- *.old
EOT
rsync -avC --include=foo.o -f :C --exclude=&#039;*.old&#039; a/ b&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both of the above rsync commands are identical.  Each one will
merge all the per-directory &lt;code&gt;.cvsignore&lt;/code&gt; rules in the middle of the
list rather than at the end.  This allows their dir-specific
rules to supersede the rules that follow the &lt;code&gt;:C&lt;/code&gt; instead of being
subservient to all your rules.  To affect the other CVS exclude
rules (i.e. the default list of exclusions, the contents of
&lt;code&gt;$HOME/.cvsignore&lt;/code&gt;, and the value of &lt;code&gt;$CVSIGNORE&lt;/code&gt;) you should omit
the &lt;code&gt;-C&lt;/code&gt; command-line option and instead insert a &lt;code&gt;&quot;-C&quot;&lt;/code&gt; rule into
your filter rules; e.g.  &lt;code&gt;&quot;--filter=-C&quot;&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;List-clearing+filter+rule&quot; name=&quot;List-clearing+filter+rule&quot;&gt;List-clearing filter rule&lt;/h2&gt;
&lt;p&gt;You can clear the current include/exclude list by using the &lt;code&gt;&quot;!&quot;&lt;/code&gt;
filter rule (as introduced in the
&lt;strong&gt;FILTER RULES&lt;/strong&gt;
section above).
The &lt;em&gt;&amp;quot;current&amp;quot;&lt;/em&gt; list is either the global list of rules (if the
rule is encountered while parsing the filter options) or a set of
per-directory rules (which are inherited in their own sub-list,
so a subdirectory can use this to clear out the parent&#039;s rules).&lt;/p&gt;
&lt;h2 id=&quot;Anchoring+include%2Fexclude+patterns&quot; name=&quot;Anchoring+include%2Fexclude+patterns&quot;&gt;Anchoring include/exclude patterns&lt;/h2&gt;
&lt;p&gt;As mentioned earlier, global include/exclude patterns are
anchored at the &amp;quot;root of the transfer&amp;quot; (as opposed to per-
directory patterns, which are anchored at the merge-file&#039;s
directory).  If you think of the transfer as a subtree of names
that are being sent from sender to receiver, the transfer-root is
where the tree starts to be duplicated in the destination
directory.  This root governs where patterns that start with a /
match.&lt;/p&gt;
&lt;p&gt;Because the matching is relative to the transfer-root, changing
the trailing slash on a source path or changing your use of the
&lt;code&gt;--relative&lt;/code&gt; option affects the path you need to use in your
matching (in addition to changing how much of the file tree is
duplicated on the destination host).  The following examples
demonstrate this.&lt;/p&gt;
&lt;p&gt;Let&#039;s say that we want to match two source files, one with an
absolute path of &lt;code&gt;&quot;/home/me/foo/bar&quot;&lt;/code&gt;, and one with a path of
&lt;code&gt;&quot;/home/you/bar/baz&quot;&lt;/code&gt;.  Here is how the various command choices
differ for a 2-source transfer:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Example cmd: rsync -a /home/me /home/you /dest
+/- pattern: /me/foo/bar
+/- pattern: /you/bar/baz
Target file: /dest/me/foo/bar
Target file: /dest/you/bar/baz

Example cmd: rsync -a /home/me/ /home/you/ /dest
+/- pattern: /foo/bar               (note missing &quot;me&quot;)
+/- pattern: /bar/baz               (note missing &quot;you&quot;)
Target file: /dest/foo/bar
Target file: /dest/bar/baz

Example cmd: rsync -a --relative /home/me/ /home/you /dest
+/- pattern: /home/me/foo/bar       (note full path)
+/- pattern: /home/you/bar/baz      (ditto)
Target file: /dest/home/me/foo/bar
Target file: /dest/home/you/bar/baz

Example cmd: cd /home; rsync -a --relative me/foo you/ /dest
+/- pattern: /me/foo/bar      (starts at specified path)
+/- pattern: /you/bar/baz     (ditto)
Target file: /dest/me/foo/bar
Target file: /dest/you/bar/baz&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The easiest way to see what name you should filter is to just
look at the output when using &lt;code&gt;--verbose&lt;/code&gt; and put a &lt;code&gt;/&lt;/code&gt; in front of
the name (use the &lt;code&gt;--dry-run&lt;/code&gt; option if you&#039;re not yet ready to
copy any files).&lt;/p&gt;
&lt;h2 id=&quot;Per-directory+rules+and+delete&quot; name=&quot;Per-directory+rules+and+delete&quot;&gt;Per-directory rules and delete&lt;/h2&gt;
&lt;p&gt;Without a delete option, per-directory rules are only relevant on
the sending side, so you can feel free to exclude the merge files
themselves without affecting the transfer.  To make this easy,
the &lt;code&gt;&#039;e&#039;&lt;/code&gt; modifier adds this exclude for you, as seen in these two
equivalent commands:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rsync -av --filter=&#039;: .excl&#039; --exclude=.excl host:src/dir /dest
rsync -av --filter=&#039;:e .excl&#039; host:src/dir /dest&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, if you want to do a delete on the receiving side AND you
want some files to be excluded from being deleted, you&#039;ll need to
be sure that the receiving side knows what files to exclude.  The
easiest way is to include the per-directory merge files in the
transfer and use &lt;code&gt;--delete-after&lt;/code&gt;, because this ensures that the
receiving side gets all the same exclude rules as the sending
side before it tries to delete anything:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rsync -avF --delete-after host:src/dir /dest&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, if the merge files are not a part of the transfer,
you&#039;ll need to either specify some global exclude rules (i.e.
specified on the command line), or you&#039;ll need to maintain your
own per-directory merge files on the receiving side.  An example
of the first is this (assume that the remote .rules files exclude
themselves):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rsync -av --filter=&#039;: .rules&#039; --filter=&#039;. /my/extra.rules&#039; --delete host:src/dir /dest&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the above example the extra.rules file can affect both sides
of the transfer, but (on the sending side) the rules are
subservient to the rules merged from the &lt;code&gt;.rules&lt;/code&gt; files because
they were specified after the per-directory merge rule.&lt;/p&gt;
&lt;p&gt;In one final example, the remote side is excluding the
&lt;code&gt;.rsync-filter&lt;/code&gt; files from the transfer, but we want to use our own
&lt;code&gt;.rsync-filter&lt;/code&gt; files to control what gets deleted on the receiving
side.  To do this we must specifically exclude the per-directory
merge files (so that they don&#039;t get deleted) and then put rules
into the local files to control what else should not get deleted.
Like one of these commands:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rsync -av --filter=&#039;:e /.rsync-filter&#039; --delete host:src/dir /dest
rsync -avFF --delete host:src/dir /dest&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>Munin-tweaks</title>
<link href="https://www.0ink.net/posts/2023/2023-06-01-munin-tweaks.html"></link>
<id>urn:uuid:61ff4d78-2a4b-baaa-d354-8e1063795eb1</id>
<updated>2023-04-25T00:00:00+02:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Small recipes to tweak munin configurations.
Overriding critical and warning levels
In the node configuration enter:
plugin.field_name.critical value
plugin.field_name.warning value
The plugin name can be found by clicking in the graph with the value
...]]></summary>
<content type="html">&lt;p&gt;Small recipes to tweak munin configurations.&lt;/p&gt;
&lt;h2 id=&quot;Overriding+critical+and+warning+levels&quot; name=&quot;Overriding+critical+and+warning+levels&quot;&gt;Overriding critical and warning levels&lt;/h2&gt;
&lt;p&gt;In the node configuration enter:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;plugin.field_name.critical value
plugin.field_name.warning value&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The plugin name can be found by clicking in the graph with the value
you want to override.  The last component of the url (without the &lt;code&gt;.html&lt;/code&gt;)
is the plugin name.&lt;/p&gt;
&lt;p&gt;The field name is in this view under the column &lt;code&gt;Internal name&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[xenhosts;cn4.localnet]
  address cn4.localnet
  use_node_name no

  vgs.pool0_.warning 99.00
  vgs.pool0_.critical 99.50&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Reference+documentation&quot; name=&quot;Reference+documentation&quot;&gt;Reference documentation&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://guide.munin-monitoring.org/en/latest/reference/munin.conf.html#field-directives&quot;&gt;config field directives&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Desktop Environments 2023</title>
<link href="https://www.0ink.net/posts/2023/2023-05-15-de-2023.html"></link>
<id>urn:uuid:539df26d-1f8a-1661-f944-4ebe54aabe22</id>
<updated>2024-02-02T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Around April 2023, I decided to look for a new Linux Desktop Environment for my
personal void linux.  So I tried these distros:

lxqt: This is the one I eventually chose to switch to.  I liked it that it
was very small and light, and very modular, almost like a kit that you assemble
yourself.  Because I have thinking for some time that I would like to make my
...]]></summary>
<content type="html">&lt;p&gt;Around April 2023, I decided to look for a new &lt;a href=&quot;https://en.wikipedia.org/wiki/Desktop_environment&quot;&gt;Linux Desktop Environment&lt;/a&gt; for my
personal &lt;a href=&quot;https://voidlinux.org/&quot;&gt;void linux&lt;/a&gt;.  So I tried these distros:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://lxqt-project.org/about/&quot;&gt;lxqt&lt;/a&gt;: This is the one I eventually chose to switch to.  I liked it that it
was very small and light, and very modular, almost like a kit that you assemble
yourself.  Because I have thinking for some time that I would like to make my
own &lt;a href=&quot;https://en.wikipedia.org/wiki/Desktop_environment&quot;&gt;Desktop environment&lt;/a&gt; from a &lt;a href=&quot;https://en.wikipedia.org/wiki/Window_manager&quot;&gt;Window Manager&lt;/a&gt;, &lt;a href=&quot;https://lxqt-project.org/about/&quot;&gt;lxqt&lt;/a&gt; is very close
to what I wanted to do with that idea.
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PROS&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;Light and modular&lt;/li&gt;
&lt;li&gt;Classic User Experience (great since I am very old computer user).&lt;/li&gt;
&lt;li&gt;Uses &lt;a href=&quot;https://www.jwz.org/xscreensaver/&quot;&gt;XScreenSaver&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Uses QT widget set.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CONS&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;not as visually appealing as the other desktops here.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mate-desktop.org/&quot;&gt;MATE Desktop&lt;/a&gt;: This is the desktop that I was using before.  It is light on
resources and has all the features you would expect.  Nothing against it, but I thought
it was time to move on.  Also my &lt;a href=&quot;https://github.com/alejandroliu/0ink.net/tree/main/snippets/2020/mate-screensaver-hacks&quot;&gt;XScreenSaver&lt;/a&gt; stopped working in the latest
&lt;a href=&quot;https://voidlinux.org/&quot;&gt;void&lt;/a&gt; update.
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PROS&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Full feature yet light on resources&lt;/li&gt;
&lt;li&gt;Classic User Experience (great since I am very old computer user).&lt;/li&gt;
&lt;li&gt;Was possible to use &lt;a href=&quot;https://github.com/alejandroliu/0ink.net/tree/main/snippets/2020/mate-screensaver-hacks&quot;&gt;XScreenSaver&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.gnome.org/&quot;&gt;standard gnome&lt;/a&gt;: This is supposed to be the &lt;strong&gt;Premier&lt;/strong&gt; Linux [Desktop Environment].
Personally, I find it hard to use, as it completely deviates from User Experienced of
&lt;a href=&quot;https://microsoft.fandom.com/wiki/Windows_95&quot;&gt;Microsoft Windows 95&lt;/a&gt;.  WHile it may be innovative, for me, an old computer user,
it just gets in the way of getting things done.  So for me it is a total turn-off.
Also, under &lt;a href=&quot;https://voidlinux.org/&quot;&gt;void&lt;/a&gt; I was only able to use the &lt;a href=&quot;https://en.wikipedia.org/wiki/Wayland_(protocol)&quot;&gt;Wayland&lt;/a&gt; session.
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PROS&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Very modern and popular desktop&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CONS&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Too modern for my taste&lt;/li&gt;
&lt;li&gt;Could only run on &lt;a href=&quot;https://en.wikipedia.org/wiki/Wayland_(protocol)&quot;&gt;wayland&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wiki.gnome.org/Projects/GnomeFlashback&quot;&gt;gnome flashback&lt;/a&gt; : This returns the &lt;a href=&quot;https://www.gnome.org/&quot;&gt;gnome&lt;/a&gt; desktop back to a &lt;em&gt;classic&lt;/em&gt;
user experience.  I personally find this UX much better than the &lt;a href=&quot;https://www.gnome.org/&quot;&gt;standard gnome&lt;/a&gt; user
experience.  Visually, was very smooth and appealing.  I found it quite pleasant.  I was also
able to run it as part of the X session.
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PROS&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Buttery smooth look and feel&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kde.org/plasma-desktop/&quot;&gt;KDE plasma&lt;/a&gt; : This is the &lt;a href=&quot;https://kde.org/plasma-desktop/&quot;&gt;KDE project&lt;/a&gt; &lt;a href=&quot;https://en.wikipedia.org/wiki/Desktop_environment&quot;&gt;Desktop Environment&lt;/a&gt;.  I found
it OK, but not particularly special.  I just tried it on &lt;a href=&quot;https://voidlinux.org/&quot;&gt;void&lt;/a&gt; and managed it to get it
to work and do stuff, but didn&#039;t feel worth it to stick around too much.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.xfce.org/&quot;&gt;XFCE4&lt;/a&gt;: This is another classic UX &lt;a href=&quot;https://en.wikipedia.org/wiki/Desktop_environment&quot;&gt;Desktop Environment&lt;/a&gt;.  Just like &lt;a href=&quot;https://mate-desktop.org/&quot;&gt;MATE&lt;/a&gt;
is a full featured desktop that tends to be light on resources.  I find it stable and a
good performer.  The main reason why I did not opt for &lt;a href=&quot;https://www.xfce.org/&quot;&gt;xfce&lt;/a&gt; is that this is the desktop
I usually use for Virtual Machines that I spin up on the cloud.  So it is useful to have a
visually different UX so as not to get confused between working locally and working on a cloud
system.
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PROS&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;Classic User Experience (great since I am very old computer user).&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Budgie_(desktop_environment)&quot;&gt;budgie&lt;/a&gt;: Did not work for me on &lt;a href=&quot;https://voidlinux.org/&quot;&gt;void linux&lt;/a&gt;.  It would start, but the mouse
and keyboard would be unresponsive.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Cinnamon_(desktop_environment)&quot;&gt;cinnamon&lt;/a&gt;: Did not work for me on &lt;a href=&quot;https://voidlinux.org/&quot;&gt;void linux&lt;/a&gt;.  It would start, but the mouse
and keyboard would be unresponsive.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.fosslinux.com/4652/pantheon-everything-you-need-to-know-about-the-elementary-os-desktop.htm&quot;&gt;pantheon&lt;/a&gt;: This is a desktop that I would like to try, but it is
not available on &lt;a href=&quot;https://voidlinux.org/&quot;&gt;Void Linux&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Pet+Peeves&quot; name=&quot;Pet+Peeves&quot;&gt;Pet Peeves&lt;/h2&gt;
&lt;h3 id=&quot;Wayland&quot; name=&quot;Wayland&quot;&gt;Wayland&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://en.wikipedia.org/wiki/Wayland_(protocol)&quot;&gt;Wayland&lt;/a&gt; project started back in 2008.  It started as a modern Window
system implementation, slated to replace &lt;a href=&quot;https://en.wikipedia.org/wiki/X.Org_Server&quot;&gt;Xorg&lt;/a&gt;.  I am writing this is 2023,
and after 15 years, it is only partially available.  As far as I can tell, the only
desktop environments that support &lt;a href=&quot;https://en.wikipedia.org/wiki/Wayland_(protocol)&quot;&gt;wayland&lt;/a&gt; are &lt;a href=&quot;https://www.gnome.org/&quot;&gt;gnome&lt;/a&gt; and
&lt;a href=&quot;https://kde.org/plasma-desktop/&quot;&gt;plasma&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I think this is a pity, but that is how things are in Open Source without a real
corporate sponsor.&lt;/p&gt;
&lt;h3 id=&quot;GTK%2B+libraries&quot; name=&quot;GTK%2B+libraries&quot;&gt;GTK+ libraries&lt;/h3&gt;
&lt;p&gt;I find this is a bit of a mess.  I think it is ude with how the &lt;a href=&quot;https://www.gnome.org/&quot;&gt;gnome&lt;/a&gt; project
is evolving it software components.  I use &lt;a href=&quot;https://voidlinux.org/&quot;&gt;void linux&lt;/a&gt; and at the time of this
writing I can install from the &lt;a href=&quot;https://voidlinux.org/&quot;&gt;void&lt;/a&gt; repositories three different versions of
&lt;a href=&quot;https://en.wikipedia.org/wiki/GTK&quot;&gt;GTK+&lt;/a&gt;, v2, v3 and v4.  Also, I tried compiling a package called &lt;a href=&quot;https://xnee.wordpress.com/&quot;&gt;Xnee&lt;/a&gt; and
that actually seemed to require v1.&lt;/p&gt;
&lt;p&gt;Anyways, because different programs require different &lt;a href=&quot;https://en.wikipedia.org/wiki/GTK&quot;&gt;GTK+&lt;/a&gt; release levels, this
means that my working &lt;a href=&quot;https://voidlinux.org/&quot;&gt;void&lt;/a&gt; desktop has all three versions installed.&lt;/p&gt;
&lt;p&gt;I personally find this quite messy.  Again, this is a fact of life with fully Open Source
Software.&lt;/p&gt;</content>
</entry>
<entry>
<title>Personal thoughts on GUI programming</title>
<link href="https://www.0ink.net/posts/2023/2023-05-01-gui-programming.html"></link>
<id>urn:uuid:85d14b91-106c-a764-5f5a-1d41bfcd946e</id>
<updated>2024-02-02T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[I have been programming for about 40 years.  Going through a lot of different languages
and programming paradims.
Lately most of my programming is done in:

bash/shell script
Python
...]]></summary>
<content type="html">&lt;p&gt;I have been programming for about 40 years.  Going through a lot of different languages
and programming paradims.&lt;/p&gt;
&lt;p&gt;Lately most of my programming is done in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.gnu.org/software/bash/&quot;&gt;bash/shell&lt;/a&gt; script&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.python.org/&quot;&gt;Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.php.net/manual/en/intro-whatis.php&quot;&gt;PHP&lt;/a&gt;, with some bits in &lt;a href=&quot;https://en.wikipedia.org/wiki/JavaScript&quot;&gt;JavaScript&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So lately, after hitting some bugs in a recent update of the &lt;a href=&quot;https://mate-desktop.org/&quot;&gt;MATE&lt;/a&gt; desktop environment
I decided to re-do my desktop set-up switching to a new desktop environment.  After testing
several desktop environments I decided for &lt;a href=&quot;https://lxqt-project.org/about/&quot;&gt;LxQt&lt;/a&gt; because of its minimalistic feel.
While doing this, I figured, I needed some GUI scripts to spice things up.&lt;/p&gt;
&lt;p&gt;In the past, most of these simple GUI scripts, I have done them in &lt;a href=&quot;https://www.tcl.tk/&quot;&gt;TCL/TK&lt;/a&gt;. So I am very
familiar writting GUI applications using &lt;a href=&quot;https://www.tcl.tk/&quot;&gt;TCL&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Since, &lt;a href=&quot;https://www.tcl.tk/&quot;&gt;TCL&lt;/a&gt; is not a popular language (in a &lt;a href=&quot;https://distantjob.com/blog/programming-languages-rank/&quot;&gt;survey at the beginning of 2023&lt;/a&gt; it
wasn&#039;t even mentioned.  On the other hand, &lt;a href=&quot;https://www.python.org/&quot;&gt;python&lt;/a&gt; seemed quite popular.&lt;/p&gt;
&lt;p&gt;I decided, that it probably would be a good idea to write these GUI scripts in &lt;a href=&quot;https://www.python.org/&quot;&gt;python&lt;/a&gt;.
Since, in &lt;a href=&quot;https://www.python.org/&quot;&gt;python&lt;/a&gt; you can also get a &lt;a href=&quot;https://en.wikipedia.org/wiki/Tkinter&quot;&gt;tkinter&lt;/a&gt; module that seems to be
quite ubiquitous.  i.e. &lt;em&gt;&amp;quot;batteries included&amp;quot;&lt;/em&gt; distributions have it, and most Linux distributions
package it.  Also, since is supposed to be a straight-port from &lt;a href=&quot;https://www.tcl.tk/&quot;&gt;TCL/TK&lt;/a&gt; I though that
the learning curve would be pretty smooth.&lt;/p&gt;
&lt;p&gt;So, I tried it using for a couple of scripts, a &lt;a href=&quot;https://github.com/alejandroliu/0ink.net/tree/main/snippets/2020/pa-hints&quot;&gt;volume control&lt;/a&gt; and a [macro recording][xm]
utilities.&lt;/p&gt;
&lt;p&gt;I would have to admit that I find writting &lt;a href=&quot;https://en.wikipedia.org/wiki/Tkinter&quot;&gt;python3 tkinter&lt;/a&gt; GUIs very awkward.  Because
it is a direct translation of &lt;a href=&quot;https://www.tcl.tk/&quot;&gt;TCL/TK&lt;/a&gt; I keep thinking in &lt;a href=&quot;https://www.tcl.tk/&quot;&gt;TCL&lt;/a&gt; terms, but things do
not work the same in &lt;a href=&quot;https://www.python.org/&quot;&gt;python&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Because, it was easier for me, the other GUI utilities, &lt;a href=&quot;https://github.com/alejandroliu/0ink.net/tree/main/snippets/2023/local-startup&quot;&gt;local-startup&lt;/a&gt; and
&lt;a href=&quot;https://github.com/alejandroliu/0ink.net/tree/main/snippets/2023/global-hotkeys&quot;&gt;hk_helper&lt;/a&gt; were written in &lt;a href=&quot;https://www.tcl.tk/&quot;&gt;TCL&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So, my conclusion is that while I can write GUI scripts in &lt;a href=&quot;https://www.python.org/&quot;&gt;python&lt;/a&gt;, the learning
curve is still steep, and I need to get a lot more experience before I can consider myself
familiar with it.&lt;/p&gt;
&lt;p&gt;Also, the decision to switch to &lt;a href=&quot;https://www.tcl.tk/&quot;&gt;tcl&lt;/a&gt; for the scripts that were eventually written
in &lt;a href=&quot;https://www.tcl.tk/&quot;&gt;tcl&lt;/a&gt;, was driven not just for the familiarity, but because the use case required
spawning new processes, which in &lt;a href=&quot;https://www.tcl.tk/&quot;&gt;tcl&lt;/a&gt; is far easier than in &lt;a href=&quot;https://www.python.org/&quot;&gt;python&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Sometimes I think using a more &lt;em&gt;pythonic&lt;/em&gt; GUI library instead of &lt;a href=&quot;https://en.wikipedia.org/wiki/Tkinter&quot;&gt;tkinter&lt;/a&gt; would
be better.  But, like I mentioned earlier, &lt;a href=&quot;https://en.wikipedia.org/wiki/Tkinter&quot;&gt;tkinter&lt;/a&gt; gets the job done, and can
be found with &lt;a href=&quot;https://www.python.org/&quot;&gt;python&lt;/a&gt; very often.&lt;/p&gt;</content>
</entry>
<entry>
<title>NacoWiki</title>
<link href="https://www.0ink.net/posts/2023/2023-04-15-nacowiki.html"></link>
<id>urn:uuid:212c8a88-04c6-4bfb-3d86-29f863286bf6</id>
<updated>2023-04-15T00:00:00+02:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[A few months ago I extensibly modify picowiki and creeated NanoWiki.
After using NanoWiki for a few months, the code became somewhat of an
spaghetti mess.  This has to do that picowiki was designed as a single
file, single class application with Plugin extension.  Since every change went
to a single class this quickly became difficult to manage.
Additionally, I realized that &quot;NanoWiki&quot; was not a very good name as this
...]]></summary>
<content type="html">&lt;p&gt;A few months ago I extensibly modify &lt;a href=&quot;https://github.com/luckyshot/picowiki&quot;&gt;picowiki&lt;/a&gt; and creeated &lt;a href=&quot;https://github.com/iliu-net/nanowiki&quot;&gt;NanoWiki&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;After using &lt;a href=&quot;https://github.com/iliu-net/nanowiki&quot;&gt;NanoWiki&lt;/a&gt; for a few months, the code became somewhat of an
spaghetti mess.  This has to do that &lt;a href=&quot;https://github.com/luckyshot/picowiki&quot;&gt;picowiki&lt;/a&gt; was designed as a single
file, single class application with Plugin extension.  Since every change went
to a single class this quickly became difficult to manage.&lt;/p&gt;
&lt;p&gt;Additionally, I realized that &amp;quot;NanoWiki&amp;quot; was not a very good name as this
name was used by several projects and organizations.&lt;/p&gt;
&lt;p&gt;So, I went ahead and re-wrote the whole thing into &lt;a href=&quot;https://github.com/iliu-net/NacoWiki/&quot;&gt;NacoWiki&lt;/a&gt;.  These are
the changes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cleaner and more functional UI.&lt;/li&gt;
&lt;li&gt;Initial REST-API support.&lt;/li&gt;
&lt;li&gt;Added a CLI interface.&lt;/li&gt;
&lt;li&gt;Off-tree installation, with the option of co-existing multiple instances.&lt;/li&gt;
&lt;li&gt;Code modularization,
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;nacowiki&lt;/code&gt; main class that integrates everything together.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Core&lt;/code&gt;: main WIKI functionality&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Cli&lt;/code&gt;: CLI interface&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PluginCollection&lt;/code&gt;: plugin support&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CodeMirror&lt;/code&gt; support now in the &lt;code&gt;Core&lt;/code&gt; (instead of depending of Plugin implementations.&lt;/li&gt;
&lt;li&gt;Re-organized CSS files&lt;/li&gt;
&lt;li&gt;Raw/source code display.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As plugins:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Page handlers:
&lt;ul&gt;
&lt;li&gt;HTML&lt;/li&gt;
&lt;li&gt;Markdown&lt;/li&gt;
&lt;li&gt;&lt;em&gt;NEW&lt;/em&gt; source code&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;NEW&lt;/em&gt; YouTube Links&lt;/li&gt;
&lt;li&gt;&lt;em&gt;NEW&lt;/em&gt; Static site generator&lt;/li&gt;
&lt;li&gt;Emojis&lt;/li&gt;
&lt;li&gt;File includes&lt;/li&gt;
&lt;li&gt;Var snippets&lt;/li&gt;
&lt;li&gt;WikiLinks (&lt;em&gt;NEW&lt;/em&gt;: search article names)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Features/improvements over &lt;a href=&quot;https://github.com/luckyshot/picowiki&quot;&gt;picowiki&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;file management: create, delete, rename, modify, attach, etc.&lt;/li&gt;
&lt;li&gt;hooks for access control&lt;/li&gt;
&lt;li&gt;meta data support&lt;/li&gt;
&lt;li&gt;Disabled code execution.  This can be considered a &lt;em&gt;&amp;quot;security&amp;quot;&lt;/em&gt; feature.&lt;/li&gt;
&lt;li&gt;Support for byte ranges.  This lets you stream video files directly
from the wiki.&lt;/li&gt;
&lt;li&gt;toggable, folder or document views.&lt;/li&gt;
&lt;li&gt;theme support&lt;/li&gt;
&lt;li&gt;Multiple file type handling&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Documentation&quot; name=&quot;Documentation&quot;&gt;Documentation&lt;/h2&gt;
&lt;p&gt;Documentation is now handled by &lt;a href=&quot;https://www.phpdoc.org/&quot;&gt;phpDocumentor&lt;/a&gt; plus HTML generated using
&lt;a href=&quot;https://github.com/iliu-net/NacoWiki/&quot;&gt;NacoWiki&lt;/a&gt;&#039;s own &lt;code&gt;SiteGen&lt;/code&gt; plugin.  You can find that here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://iliu-net.github.io/NacoWiki/php-api/&quot;&gt;phpdoc generated documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://iliu-net.github.io/NacoWiki/&quot;&gt;NacoWiki SiteGen generated docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Python Development 2023</title>
<link href="https://www.0ink.net/posts/2023/2023-04-01-python-dev-1.html"></link>
<id>urn:uuid:55a004eb-f748-7b55-bf7a-983918e0195f</id>
<updated>2023-04-25T00:00:00+02:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[
Python development on Windows
Distributing Python scripts as single EXE or Directory
Installing netifaces on Windows
Documentation generation

...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Python+development+on+Windows&quot;&gt;Python development on Windows&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Distributing+Python+scripts+as+single+EXE+or+Directory&quot;&gt;Distributing Python scripts as single EXE or Directory&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Installing+netifaces+on+Windows&quot;&gt;Installing netifaces on Windows&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Documentation+generation&quot;&gt;Documentation generation&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Using+%5Bsphinx%5D%5Bsphinx%5D+for+documentation&quot;&gt;Using [sphinx][sphinx] for documentation&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Example+docstring&quot;&gt;Example docstring&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Passing+Reserved+Keywords+as+Keyword+arguments&quot;&gt;Passing Reserved Keywords as Keyword arguments&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;Python+development+on+Windows&quot; name=&quot;Python+development+on+Windows&quot;&gt;Python development on Windows&lt;/h2&gt;
&lt;p&gt;For windows, you can use &lt;a href=&quot;https://winpython.github.io/&quot;&gt;WinPython&lt;/a&gt;.
I prefer to use this instead of the official distribution because:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It is a portable distro.&lt;/li&gt;
&lt;li&gt;You can choose for a batteries included, or just the &lt;code&gt;dot&lt;/code&gt; release
which only contains Python and Pip.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This way I can have multiple versions available.  This is particularly
useful for me because of my &lt;a href=&quot;https://open-telekom-cloud.com&quot; title=&quot;Open Telekom Cloud&quot;&gt;OTC&lt;/a&gt; development which uses
&lt;a href=&quot;https://wiki.openstack.org/wiki/SDKs&quot;&gt;OpenStack SDK&lt;/a&gt;.  In my Windows system, I was only able
to make it work with Python v3.8.10 (due to some ancient dependancies).
Because I usually don&#039;t have a compiler, I can only use binary distros
and I am unable to find it for a newer Python version.&lt;/p&gt;
&lt;h2 id=&quot;Distributing+Python+scripts+as+single+EXE+or+Directory&quot; name=&quot;Distributing+Python+scripts+as+single+EXE+or+Directory&quot;&gt;Distributing Python scripts as single EXE or Directory&lt;/h2&gt;
&lt;p&gt;When distributing I had good results with &lt;a href=&quot;https://pyinstaller.org/en/stable/&quot;&gt;pyinstaller&lt;/a&gt;.
You can create a single folder or a single exe distribution.  The
results work suprisingly well (if they work).  Some hints when using
&lt;a href=&quot;https://pyinstaller.org/en/stable/&quot;&gt;pyinstaller&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cross packaging is not possible.  If you need a Windows package
you need to run it on Windows.&lt;/li&gt;
&lt;li&gt;Dependancies not always work correctly.  You may need to use
these options:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--hidden-import module&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--collect-data module&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--copy-metadata module&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--collect-all package&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;In some cases, you may need to force the inclusion of non-python
files.  Use:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;set sitedir=%WINPYDIR%\Lib\site-packages&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;And in the command line:&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--add-data %sitedir%\path\to\data\file;path\to\data&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;I use the &lt;code&gt;%sitedir%&lt;/code&gt; variable to find things in the Python packages
directory.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;It is best to create a batch file to issue the &lt;code&gt;pyinstaller&lt;/code&gt; command.&lt;/li&gt;
&lt;li&gt;Because the command-line could become quite long, you can use the &lt;code&gt;^&lt;/code&gt;
escape.  Example:
&lt;pre&gt;&lt;code&gt;pyinstaller %buildtype% ^
  --hidden-import keystoneauth1 ^
  --collect-data keystoneauth1 ^
  --copy-metadata keystoneauth1 ^
  --hidden-import os_service_types ^
  --collect-data os_service_types ^
  --copy-metadata os_service_types ^
  --collect-all openstacksdk ^
  --copy-metadata openstacksdk ^
  --add-data %sitedir%\openstack\config\defaults.json;openstack\config ^
  --hidden-import keystoneauth1.loading._plugins ^
  --hidden-import keystoneauth1.loading._plugins.identity ^
  --hidden-import keystoneauth1.loading._plugins.identity.generic ^
urotc.py&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Installing+netifaces+on+Windows&quot; name=&quot;Installing+netifaces+on+Windows&quot;&gt;Installing &lt;code&gt;netifaces&lt;/code&gt; on Windows&lt;/h2&gt;
&lt;p&gt;NOTE: I tested this on Feb 2023.  See
&lt;a href=&quot;https://allones.de/2018/11/05/python-netifaces-installation-microsoft-visual-c-14-0-is-required/&quot;&gt;Original article here&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;netifaces&lt;/code&gt; is a &lt;a href=&quot;https://wiki.openstack.org/wiki/SDKs&quot;&gt;OpenStack SDK&lt;/a&gt; dependancy.  Under
version 3.8.10 I am able to install using &lt;code&gt;--only-binary=netifaces&lt;/code&gt; option.&lt;/p&gt;
&lt;p&gt;For newer versions it will fail with &lt;code&gt;Microsoft Visual C++ 14.0 is required&lt;/code&gt;
error message.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;C:\RH&amp;gt;pip install netifaces
Collecting netifaces
  Downloading https://files.pythonhosted.org/packages/81/39/4e9a026265ba944ddf1fea176dbb29e0fe50c43717ba4fcf3646d099fe38/netifaces-0.10.7.tar.gz
Installing collected packages: netifaces
  Running setup.py install for netifaces ... error
    Complete output from command c:\users\rh\appdata\local\programs\python\python37\python.exe -u -c &quot;import setuptools, tokenize;__file__=&#039;C:\\Users\\RH\\AppData\\Local\\Temp\\pip-install-wbfanly3\\netifaces\\setup.py&#039;;f=getattr(tokenize, &#039;open&#039;, open)(__file__);code=f.read().replace(&#039;\r\n&#039;, &#039;\n&#039;);f.close();exec(compile(code, __file__, &#039;exec&#039;))&quot; install --record C:\Users\RONALD~1.HEI\AppData\Local\Temp\pip-record-m26yfbyt\install-record.txt --single-version-externally-managed --compile:
    running install
    running build
    running build_ext
    building &#039;netifaces&#039; extension
        error: Microsoft Visual C++ 14.0 is required. Get it with &quot;Microsoft Visual C++ Build Tools&quot;: http://landinghub.visualstudio.com/visual-cpp-build-tools&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since the suggested URL doesn&#039;t work.  You need to do the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Go to the Microsoft-Repository
&lt;a href=&quot;https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2017&quot;&gt;Tools for Visual Studio 2017&lt;/a&gt;
or use the direct link to
&lt;a href=&quot;https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools&amp;amp;rel=15&quot;&gt;vs_buildtools.exe&lt;/a&gt;.
&lt;ul&gt;
&lt;li&gt;... it’s about 1.2MB&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;run „vs_buildtools.exe“
&lt;ul&gt;
&lt;li&gt;it downloads ~ 70 MB&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Select &lt;code&gt;Workloads =&amp;gt; Windows =&amp;gt; [x] Visual C++ Build Tools“ =&amp;gt; [Install]&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;it downloads 1.12 GB&lt;/li&gt;
&lt;li&gt;and installs&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Re-boot (I don&#039;t know if it is required, but I did it just in case)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now &lt;code&gt;netifaces&lt;/code&gt; can get installed:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;C:\RH&amp;gt;pip install netifaces
Collecting netifaces
  Using cached https://files.pythonhosted.org/packages/81/39/4e9a026265ba944ddf1fea176dbb29e0fe50c43717ba4fcf3646d099fe38/netifaces-0.10.7.tar.gz
Installing collected packages: netifaces
  Running setup.py install for netifaces ... done
Successfully installed netifaces-0.10.7&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;Documentation+generation&quot; name=&quot;Documentation+generation&quot;&gt;Documentation generation&lt;/h2&gt;
&lt;p&gt;When programming documentation is important, allthough very often
it takes a back seat.&lt;/p&gt;
&lt;p&gt;To help keep it up to date, it is good to make it so it is easier to
maintain and update.  One way to do that is with keeping documentation
and code together, and automating the way documentation is generated.&lt;/p&gt;
&lt;p&gt;There is a number of solutions to do this.  The one I looked at the most
were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.mkdocs.org/&quot;&gt;mkdocs&lt;/a&gt; with &lt;a href=&quot;https://mkdocstrings.github.io/python/&quot;&gt;mkdocstrings&lt;/a&gt; :
which is nice because it uses &lt;code&gt;markdown&lt;/code&gt;, however, because it is
essentially a static site generator, a lot of things needed to be done manually.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.sphinx-doc.org/en/master/&quot;&gt;sphinx&lt;/a&gt; : At the end, &lt;a href=&quot;https://www.sphinx-doc.org/en/master/&quot;&gt;sphinx&lt;/a&gt; was the option that I liked
the most.  It uses &lt;a href=&quot;https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html&quot;&gt;RST&lt;/a&gt;
for markup which is different from &lt;code&gt;markdown&lt;/code&gt;, but it is close enough.
Also, &lt;a href=&quot;https://www.sphinx-doc.org/en/master/&quot;&gt;sphinx&lt;/a&gt; can also support &lt;code&gt;markdown&lt;/code&gt; via some extensions
but I did not try that.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Using+%5Bsphinx%5D%5Bsphinx%5D+for+documentation&quot; name=&quot;Using+%5Bsphinx%5D%5Bsphinx%5D+for+documentation&quot;&gt;Using &lt;a href=&quot;https://www.sphinx-doc.org/en/master/&quot;&gt;sphinx&lt;/a&gt; for documentation&lt;/h3&gt;
&lt;p&gt;Prepare your environment:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install sphinx sphinx-argparse&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In your project directory, I have two folders:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;docs&lt;/code&gt; : where the documentation source resides&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src&lt;/code&gt; : where the &lt;code&gt;python&lt;/code&gt; code resides&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Also, I ignore:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;public&lt;/code&gt; : where the generated documentation is created.  This can
then be added into a CI pipeline to publish documentation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sphinx-quickstart&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the &lt;code&gt;docs&lt;/code&gt; folder, to initialize things.  This will create the files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;conf.py&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Makefile&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;index.rst&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Modify &lt;code&gt;conf.py&lt;/code&gt; to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;include the source:
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;sys.path.insert(0, os.path.abspath(&#039;../src&#039;))&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Customize project meta data&lt;/li&gt;
&lt;li&gt;Include enable desired extensions.  For max automation I enable:
&lt;ul&gt;
&lt;li&gt;sphinx.ext.autodoc&lt;/li&gt;
&lt;li&gt;sphinx.ext.autosummary&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Modify &lt;code&gt;Makefile&lt;/code&gt; to run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sphinx-apidoc -o apidoc ../src&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This command extracts from &lt;code&gt;python&lt;/code&gt; docstrings and creates the
relevant &lt;code&gt;rst&lt;/code&gt; files.&lt;/p&gt;
&lt;p&gt;For command line arguments, I use &lt;code&gt;arparse&lt;/code&gt; extension.  Create a
&lt;code&gt;cli.rst&lt;/code&gt; like so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CLI
===

.. argparse::
   :filename: ../src/cli.py
   :func: cli_args
   :prog: cli.py&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where &lt;code&gt;cli.py&lt;/code&gt; contains a function &lt;code&gt;cli_args&lt;/code&gt; that returns an &lt;code&gt;ArgumentParser&lt;/code&gt;
object.&lt;/p&gt;
&lt;h4 id=&quot;Example+docstring&quot; name=&quot;Example+docstring&quot;&gt;Example &lt;code&gt;docstring&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;This is an example doc string to add to your code, after the element
declaration:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;   &#039;&#039;&#039;
   Summary text

   Description of the function

   :param str argname: argument passed
   :returns bool: Returns a boolean True on success, False on failure
   &#039;&#039;&#039;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No Need to make it too complicated.&lt;/p&gt;
&lt;h2 id=&quot;Passing+Reserved+Keywords+as+Keyword+arguments&quot; name=&quot;Passing+Reserved+Keywords+as+Keyword+arguments&quot;&gt;Passing Reserved Keywords as Keyword arguments&lt;/h2&gt;
&lt;p&gt;Very often when using &lt;em&gt;wrapped&lt;/em&gt; APIs, that used functions with
Keyword arguments that you would need to pass reserved
keywords (such as &lt;code&gt;class&lt;/code&gt;, or &lt;code&gt;import&lt;/code&gt;) as function keywords.&lt;/p&gt;
&lt;p&gt;Of course, this is &lt;strong&gt;NOT&lt;/strong&gt; allowed in python.  And you will get an error
like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SyntaxError: Invalid syntax&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To work around that, you need to place those keywords in a dictionary
and use &lt;code&gt;**&lt;/code&gt; notation.  So instead of:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;response = client.service.SendSMS( toNum = &#039;0666666666666&#039;,
                pass = &#039;123456&#039;}
           )&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;you would:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;response = client.service.SendSMS( toNum = &#039;0666666666666&#039;,
                **{&#039;pass&#039;: &#039;123456&#039;}
           )&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>Raspberry Pi emulation with Qemu</title>
<link href="https://www.0ink.net/posts/2023/2023-03-01-rpi-emulation.html"></link>
<id>urn:uuid:4429db1c-f58c-8148-9ac7-b411dadd3d6f</id>
<updated>2024-02-02T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[The idea here is that we use a Desktop PC for developing/debugging
Raspberry Pi set-ups using qemu for emulating Rasperrby Pi.
qemu currently supports the following configurations:

Raspberry Pi Zero and 1A+ (armhf)
Raspberry Pi 2B (armv7)
...]]></summary>
<content type="html">&lt;p&gt;The idea here is that we use a Desktop PC for developing/debugging
Raspberry Pi set-ups using &lt;a href=&quot;https://www.qemu.org/&quot;&gt;qemu&lt;/a&gt; for emulating Rasperrby Pi.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.qemu.org/&quot;&gt;qemu&lt;/a&gt; currently supports the following configurations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Raspberry Pi Zero and 1A+ (armhf)&lt;/li&gt;
&lt;li&gt;Raspberry Pi 2B (armv7)&lt;/li&gt;
&lt;li&gt;Raspberry Pi 3A+ (aarch64)&lt;/li&gt;
&lt;li&gt;Raspberry Pi 3B (aarch64)
&lt;ul&gt;
&lt;li&gt;This is the version I am targetting in this article.  I already recycled all
my older boards.&lt;/li&gt;
&lt;li&gt;Actually I tried emulating the other configurations but they did not work.
Either they failed to boot, or the graphic display wouldn&#039;t work.&lt;/li&gt;
&lt;li&gt;So &lt;code&gt;raspi3b&lt;/code&gt; with 64-bit run-time is the only configuration I was able
to succesfully boot.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NOTE that Raspberry Pi 4 is not supported at the moment.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, unfortunately the state of things is far from perfect.&lt;/p&gt;
&lt;h2 id=&quot;Missing+display+bug&quot; name=&quot;Missing+display+bug&quot;&gt;Missing display bug&lt;/h2&gt;
&lt;p&gt;During my tests on the &lt;code&gt;raspi3b&lt;/code&gt; configuration, I was not able to get a working
console.  This has to do with this
&lt;a href=&quot;https://github.com/raspberrypi/linux/commit/6513403f73e9bdf842597d10cb0b4775ae74d165&quot;&gt;commit&lt;/a&gt;,
which disables the Frame Buffer driver because &lt;a href=&quot;https://www.qemu.org/&quot;&gt;qemu&lt;/a&gt; doesn&#039;t seem to
report the display properly.  This causes the error to show up on the kernel log:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bcm2708_fb soc:fb: Unable to determine number of FBs. Disabling driver.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Before the commit, the kernel would assume that there was always &lt;strong&gt;one&lt;/strong&gt; display.
On the other hand, this only affects use-cases that require display.  For headless
development, using the serial port works just fine.&lt;/p&gt;
&lt;p&gt;For &lt;a href=&quot;https://alpinelinux.org/&quot;&gt;Alpine Linux&lt;/a&gt;, the last working Frame Buffer version seems to be
&lt;code&gt;3.16.3-aarch64&lt;/code&gt;.  The display did not work for &lt;code&gt;3.17.0-aarch64&lt;/code&gt;.  The 32 bit
&lt;code&gt;3.16.3&lt;/code&gt; would display the &lt;code&gt;Disabling driver.&lt;/code&gt; message but I wasn&#039;t able to
boot further than that.&lt;/p&gt;
&lt;h2 id=&quot;Emulated+hardware&quot; name=&quot;Emulated+hardware&quot;&gt;Emulated hardware&lt;/h2&gt;
&lt;p&gt;According to the &lt;a href=&quot;https://www.qemu.org/docs/master/system/arm/raspi.html&quot;&gt;qemu documentation&lt;/a&gt;, the following is impleted:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ARM1176JZF-S, Cortex-A7 or Cortex-A53 CPU.  I only tested the Cortex-A53 CPU
for the &lt;code&gt;raspi3b&lt;/code&gt; configuration.&lt;/li&gt;
&lt;li&gt;Interrupt controller&lt;/li&gt;
&lt;li&gt;DMA controller&lt;/li&gt;
&lt;li&gt;Clock and reset controller (CPRMAN)&lt;/li&gt;
&lt;li&gt;System Timer&lt;/li&gt;
&lt;li&gt;GPIO controller&lt;/li&gt;
&lt;li&gt;Serial ports (BCM2835 AUX - 16550 based - and PL011)&lt;/li&gt;
&lt;li&gt;Random Number Generator (RNG)&lt;/li&gt;
&lt;li&gt;Frame Buffer : However the Linux kernel does not seem to find it.&lt;/li&gt;
&lt;li&gt;USB host (USBH)&lt;/li&gt;
&lt;li&gt;GPIO controller&lt;/li&gt;
&lt;li&gt;SD/MMC host controller&lt;/li&gt;
&lt;li&gt;SoC thermal sensor&lt;/li&gt;
&lt;li&gt;USB2 host controller (DWC2 and MPHI)&lt;/li&gt;
&lt;li&gt;MailBox controller (MBOX)&lt;/li&gt;
&lt;li&gt;VideoCore firmware (property)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As you can see, no network interface is implemented, so you must use a USB network.&lt;/p&gt;
&lt;h2 id=&quot;Getting+started&quot; name=&quot;Getting+started&quot;&gt;Getting started&lt;/h2&gt;
&lt;p&gt;The basic command line I am using is:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  qemu-system-aarch64 \
     -machine raspi3b -cpu cortex-a53 -m 1G -smp 4 -dtb bcm2710-rpi-3-b-plus.dtb \
     -kernel $linux_kernel -initrd $linux_initrd -append &quot;$cmdline&quot; \
     -sd $sd_image \
     -serial stdio \
     -usb \
     -device usb-mouse -device usb-kbd \
     -device usb-net,netdev=net0 -netdev user,id=net0,hostfwd=tcp::5555-:22&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Command explanation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;qemu-system-aarch64&lt;/code&gt; : emulate a 64-bit ARM system&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-machine raspi3b -cpu cortex-a53 -m 1G -smp 4 -dtb bcm2710-rpi-3-b-plus.dtb&lt;/code&gt; :
Matches the Raspberry Pi model 3B configuration.  The &lt;code&gt;dtb&lt;/code&gt; is a file from the
Raspberry Pi boot partition that is normally loaded by the Firmware.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-kernel $linux_kernel -initrd $linux_initrd -append &quot;$cmdline&quot;&lt;/code&gt; :
Linux related boot configuration.  You must provide a kernel and optional initrd
files.  Usually you would extract them from your &lt;code&gt;sdcard&lt;/code&gt; image.  The append
is used for the kernel command line.  If you want a serial console make sure
you include:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;console=ttyAMA0,115200&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sd $sd_image&lt;/code&gt; : Image for the &lt;code&gt;sdcard&lt;/code&gt; storage&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-usb&lt;/code&gt; : Enable USB bus.  Needed for the emulated console mouse/keyboard and usb
network.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-serial stdio&lt;/code&gt; : enable a serial console (if you are using the emulated
framebuffer.  Note that &lt;code&gt;Ctrl+C&lt;/code&gt; are not caught and would kill the emulation.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-device usb-mouse -device usb-kbd&lt;/code&gt; : these are used with the virtual framebuffer
for providing keyboard and mouse.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-device usb-net,netdev=net0 -netdev user,id=net0,hostfwd=tcp::5555-:22&lt;/code&gt; :
Enable virtual networking using &lt;a href=&quot;https://en.wikipedia.org/wiki/Slirp&quot;&gt;slirp&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you wish to run a headless (only serial console) configuration, you should
remove the &lt;code&gt;-serial stdio -device usb-mouse -device usb-kbd&lt;/code&gt; options and
just use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-nographic&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This would automatically enable &lt;code&gt;-serial stdio&lt;/code&gt; and remove the framebuffer.  In
this configuration &lt;code&gt;Ctrl-C&lt;/code&gt; is handled properly.&lt;/p&gt;
&lt;h2 id=&quot;Tested+OS&quot; name=&quot;Tested+OS&quot;&gt;Tested OS&lt;/h2&gt;
&lt;p&gt;I tested the following images, with these results:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operating System&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2020-08-24/&quot;&gt;2020-08-20-raspios-buster-arm64-lite.zip&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;fully working&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2022-09-26/&quot;&gt;2022-09-22-raspios-bullseye-arm64-lite.img.xz&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Only works &lt;em&gt;headless&lt;/em&gt;.  Default user is not set properly, so the image needs to be modified to inject login credentials&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://dl-cdn.alpinelinux.org/alpine/v3.16/releases/aarch64/&quot;&gt;alpine-rpi-3.16.3-aarch64.tar.gz&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;fully working&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://dl-cdn.alpinelinux.org/alpine/v3.17/releases/aarch64/&quot;&gt;alpine-rpi-3.17.0-aarch64.tar.gz&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Only works &lt;em&gt;headless&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;raspi-emu&quot; name=&quot;raspi-emu&quot;&gt;raspi-emu&lt;/h2&gt;
&lt;p&gt;For convenicne, I wrote the &lt;code&gt;raspi-emu&lt;/code&gt; script:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/snippets/2023/raspi-emu&quot;&gt;rasi-emu&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This can be used to prepare images and run emulation sessions.&lt;/p&gt;
&lt;p&gt;Usage:&lt;/p&gt;
&lt;h3 id=&quot;Preparing+base+image&quot; name=&quot;Preparing+base+image&quot;&gt;Preparing base image&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;raspi-emu prep [options] src&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Prepares the downloaded image so it can be used as a base for a &lt;a href=&quot;https://www.qemu.org/&quot;&gt;qemu&lt;/a&gt; thin-provisioned
image.&lt;/p&gt;
&lt;p&gt;Options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--sz=size&lt;/code&gt; : Set the base image to the given &lt;code&gt;size&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-c&lt;/code&gt; | &lt;code&gt;--compress&lt;/code&gt; : For &lt;code&gt;qcow2&lt;/code&gt; images, create a compressed image.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--qcow2&lt;/code&gt; : Create a &lt;code&gt;qcow2&lt;/code&gt; format image.  This is the default.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--raw&lt;/code&gt; : Create a &lt;code&gt;raw&lt;/code&gt; image.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--volume=name&lt;/code&gt; : When creating &lt;a href=&quot;https://alpinelinux.org/&quot;&gt;AlpineLinux&lt;/a&gt; images, use &lt;code&gt;name&lt;/code&gt; as the
volume name.  Otherwise a random name is generated.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Formatting+SDCARD+image&quot; name=&quot;Formatting+SDCARD+image&quot;&gt;Formatting SDCARD image&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;raspi-emu format [options] base dest&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create an SDCARD image to be used for &lt;a href=&quot;https://www.qemu.org/&quot;&gt;qemu&lt;/a&gt; emulation.  It will
create a thin-provisioned image when possible.&lt;/p&gt;
&lt;p&gt;Options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--resize=size&lt;/code&gt; : Set the SDCARD image to the given size.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Running+Emulation&quot; name=&quot;Running+Emulation&quot;&gt;Running Emulation&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;raspi-emu run [options] sdimg&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Will boot &lt;a href=&quot;https://www.qemu.org/&quot;&gt;qemu&lt;/a&gt; emulation with the specified SDCARD image.  Configuration
when possible is read from the boot partition of the SDCARD.&lt;/p&gt;
&lt;p&gt;Options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--vfb-only&lt;/code&gt; : Enable the virtual framebuffer and disables the serial console.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--vfb&lt;/code&gt; :  Enables the virtual framebuffer.  The serial console is kept enabled.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--no-vfb&lt;/code&gt; : Disables virtual framebuffer.  This is the default.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--ttycon&lt;/code&gt; : Enables the serial console for Linux logins.  (Default)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--no-ttycon&lt;/code&gt; : Disables the serial console for Linux logins.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--vnet&lt;/code&gt; : Enable virtual network.  (Default)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--no-vnet&lt;/code&gt; : Disables hte virtual network.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--portfwd&lt;/code&gt; : Enables virtual network.  Forwards port 5555 on host to port 22 on VM.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--portfwd=rule&lt;/code&gt; : Adds the given port forwarding rule. &lt;br /&gt;Example rule: &lt;code&gt;tcp::5555-:22&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--no-portfwd&lt;/code&gt; : Dsiables port forwarding.  (Default)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--raspi3b&lt;/code&gt; : Emulate a Raspberry Pi Model 3B.  (Default)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The default is running headless (only serial console) with networking enabled.&lt;/p&gt;</content>
</entry>
<entry>
<title>Home Assistant Wall Panel</title>
<link href="https://www.0ink.net/posts/2023/2023-02-20-wallpanel.html"></link>
<id>urn:uuid:d92016f2-d991-eb55-cbac-f2ea92ace405</id>
<updated>2023-01-11T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[For a while I was using TabletClock with old tablets.  But
this has not been updated in a while.  I was thinking of writing my
own version until I found WallPanel.
Essentially it is purposely built web-browser with special features which makes it
possible to use it to replaces TabletClock.  Essentially, I could
replace TabletClock with a web-page showing the time and a weather
...]]></summary>
<content type="html">&lt;p&gt;For a while I was using &lt;a href=&quot;https://github.com/HyperTechnology5/TabletClock&quot;&gt;TabletClock&lt;/a&gt; with old tablets.  But
this has not been updated in a while.  I was thinking of writing my
own version until I found &lt;a href=&quot;https://github.com/thecowan/wallpanel-android&quot;&gt;WallPanel&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Essentially it is purposely built web-browser with special features which makes it
possible to use it to replaces &lt;a href=&quot;https://github.com/HyperTechnology5/TabletClock&quot;&gt;TabletClock&lt;/a&gt;.  Essentially, I could
replace &lt;a href=&quot;https://github.com/HyperTechnology5/TabletClock&quot;&gt;TabletClock&lt;/a&gt; with a web-page showing the time and a weather
forecast and have &lt;a href=&quot;https://github.com/thecowan/wallpanel-android&quot;&gt;WallPanel&lt;/a&gt; point to it.  Easy and simple, and can be
customized in multiple ways.&lt;/p&gt;
&lt;p&gt;Currently I am using &lt;a href=&quot;https://github.com/thecowan/wallpanel-android&quot;&gt;WallPanel&lt;/a&gt; with &lt;a href=&quot;https://www.home-assistant.io/&quot;&gt;HomeAssistant&lt;/a&gt;.  I am using
these features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Date/time screen saver with auto off by face recognition&lt;/li&gt;
&lt;li&gt;Web Cam functionality&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I normally have it on a &lt;a href=&quot;https://www.home-assistant.io/&quot;&gt;HomeAssistant&lt;/a&gt; panel showing time, weather forecaset
and a few choice controls.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2023/wallpanel.png&quot; alt=&quot;panel&quot; /&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>RDP vs VNC</title>
<link href="https://www.0ink.net/posts/2023/2023-02-10-rdp-vs-vnc.html"></link>
<id>urn:uuid:fa9eb052-d45a-370e-8851-89ecfa097950</id>
<updated>2023-01-11T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[For years I have been using VNC for my remote desktop needs.  This works
usually well enough.  The features that I like are:

Basic set-up is easy
Desktop sessions are persistent
Can be used to view an actual X11.org desktop.
...]]></summary>
<content type="html">&lt;p&gt;For years I have been using &lt;a href=&quot;https://en.wikipedia.org/wiki/Virtual_Network_Computing&quot;&gt;VNC&lt;/a&gt; for my remote desktop needs.  This works
usually well enough.  The features that I like are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Basic set-up is easy&lt;/li&gt;
&lt;li&gt;Desktop sessions are persistent&lt;/li&gt;
&lt;li&gt;Can be used to view an actual X11.org desktop.&lt;/li&gt;
&lt;li&gt;Browser based clients via &lt;a href=&quot;https://novnc.com/info.html&quot;&gt;noVNC&lt;/a&gt; or &lt;a href=&quot;https://guacamole.apache.org/&quot;&gt;Guacamole&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On the other hand, a number of features are either not implemented or are
not easily implementable.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;On-demand desktop sessions.  Usually you can hack scripts to do this.  Or you
can use &lt;code&gt;inetd&lt;/code&gt; mode to create a session on-demand, however, this loses
session persistence.&lt;/li&gt;
&lt;li&gt;While in theory, because most Desktops use &lt;a href=&quot;https://en.wikipedia.org/wiki/PulseAudio&quot;&gt;pulseaudio&lt;/a&gt; which would let you
redirect audio to a remote, this is another protocol so it is not simple
to set-up in practice.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So I have now found &lt;a href=&quot;https://en.wikipedia.org/wiki/Xrdp&quot;&gt;xrdp&lt;/a&gt; which provides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Easy basic set-up&lt;/li&gt;
&lt;li&gt;On-demand desktop sessions (session management) with persistent session support.&lt;/li&gt;
&lt;li&gt;Sound redirection (however I have not been able to make this work)&lt;/li&gt;
&lt;li&gt;Browser based clients via &lt;a href=&quot;https://guacamole.apache.org/&quot;&gt;Guacamole&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For the client side, you can either use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://4it.com.au/kb/article/how-to-start-remote-desktop-rdp-from-the-command-prompt/&quot;&gt;RDP Client&lt;/a&gt; : which comes with MS-Windows or&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.freerdp.com/&quot;&gt;freerdp&lt;/a&gt; : For Linux.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Still I have not been able to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Enable sound re-direction
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://c-nergy.be/blog/?p=13655&quot;&gt;Configure sound&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Essentially compile &lt;a href=&quot;https://github.com/neutrinolabs/pulseaudio-module-xrdp&quot;&gt;module&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Use to view an actuall X11.org desktop.  For this I am simply using &lt;a href=&quot;https://en.wikipedia.org/wiki/X11vnc&quot;&gt;x11vnc&lt;/a&gt;
and just using the &lt;code&gt;vncviewer&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>QNAP Snapshots</title>
<link href="https://www.0ink.net/posts/2023/2023-02-01-qsnap.html"></link>
<id>urn:uuid:139013d4-a884-034b-13de-f5915cb3c900</id>
<updated>2024-02-02T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[I wrote a small tool to access QNAP snapshots from the Linux command line.
Pre-requistes:

Snapshots have to be enabled
You need a /share/netcfg containing the file:

...]]></summary>
<content type="html">&lt;p&gt;I wrote a small tool to access QNAP snapshots from the Linux command line.&lt;/p&gt;
&lt;p&gt;Pre-requistes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Snapshots have to be enabled&lt;/li&gt;
&lt;li&gt;You need a &lt;code&gt;/share/netcfg&lt;/code&gt; containing the file:
&lt;ul&gt;
&lt;li&gt;In my case, I set this share as &lt;code&gt;read-only&lt;/code&gt; with &lt;code&gt;root-squash&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;`admin.yaml&#039; : contains the private/public keys and the configuration
of the forced command.  For access control, it is only readable
to group and the file is owned by the UNIX group that can do snapshot
operations.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;registry.yaml&lt;/code&gt; : this is optional if you are changing the &lt;code&gt;admin&lt;/code&gt; username.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Afterwards run:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;install_key.sh&lt;/code&gt; &lt;strong&gt;server-name&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This installs the public key into the &lt;code&gt;authorized keys&lt;/code&gt;.  You will need
ssh access for this to work.&lt;/p&gt;
&lt;p&gt;You need to do this on all the QNAP servers that offer snapshots.&lt;/p&gt;
&lt;p&gt;Copy &lt;code&gt;qsnap&lt;/code&gt; to somewhere in your path.&lt;/p&gt;
&lt;h2 id=&quot;Usage&quot; name=&quot;Usage&quot;&gt;Usage&lt;/h2&gt;
&lt;h3 id=&quot;Listing+snapshots&quot; name=&quot;Listing+snapshots&quot;&gt;Listing snapshots&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;qsnap&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;List snapshots for the current directory&lt;/p&gt;
&lt;p&gt;&lt;code&gt;bash qsnap ls file-path &lt;/code&gt;&lt;/p&gt;
&lt;p&gt;List snapshots for the given &lt;code&gt;file-path&lt;/code&gt;.  &lt;code&gt;file-path&lt;/code&gt; can be provided
multiple times.&lt;/p&gt;
&lt;h3 id=&quot;Reading+snapshot+files&quot; name=&quot;Reading+snapshot+files&quot;&gt;Reading snapshot files&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;qsnap cat [--snap=snapid] file1 [file2 file3 ...]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Would display the given file(s) from the snapshot.  If &lt;code&gt;snapid&lt;/code&gt; is not
specified will use the latest available snapshot.&lt;/p&gt;
&lt;h3 id=&quot;Dumping+snapshots&quot; name=&quot;Dumping+snapshots&quot;&gt;Dumping snapshots&lt;/h3&gt;
&lt;p&gt;``bash
qsnap tar [--snap=snapid] [options] path&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;

Will dump the given `path` as a tarball.  If `snapid` is not
specified will use the latest available snapshot.

The `path` can be either a file or directory.

Additional options:

- `--base64` : Data will be dumped using MIME Base64 encoding
- &#039;--no-compress&#039; : Default is to compress.  This disables compression
- &#039;-v&#039; : Pass `v` flag to `tar` command.

All this can be found on [github](https://github.com/alejandroliu/0ink.net/blob/main/snippets/2023/qsnap).&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>Docker on Void</title>
<link href="https://www.0ink.net/posts/2023/2023-01-20-docker-on-void.html"></link>
<id>urn:uuid:90c92086-4c8d-2a4c-29a1-d323f9f155ab</id>
<updated>2025-04-01T00:00:00+02:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[This is a quick recipe to run Docker on void:

Make sure your system is up-to-date:

sudo xbps-install -Syu

...]]></summary>
<content type="html">&lt;p&gt;This is a quick recipe to run Docker on void:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Make sure your system is up-to-date:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sudo xbps-install -Syu&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Install docker executables:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sudo xbps-install -S docker&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Additional recommended packaged: &lt;code&gt;docker-compose&lt;/code&gt;, &lt;code&gt;docker-buildx&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Check if docker was installed properly:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;docker --version&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Enable services:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sudo ln -s /etc/sv/containerd /var/service&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sudo ln -s /etc/sv/docker /var/service&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Add your user to the &lt;code&gt;docker&lt;/code&gt; group:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sudo usermod -a -G docker $(whoami)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can start using docker.  You may need to logout and login again for the group
membership to be updated.&lt;/p&gt;
&lt;p&gt;Here is a &lt;a href=&quot;https://docker-curriculum.com/&quot;&gt;beginners tutorial&lt;/a&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>Home Assistant sensors</title>
<link href="https://www.0ink.net/posts/2023/2023-01-10-hassio-sensors.html"></link>
<id>urn:uuid:a1c9b6f8-82f2-dfa5-ba09-b48a600085cc</id>
<updated>2023-01-11T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[I finished migrating my VeraEdge to Home Assistant.  I think after
using it for some time, I find Home Assistant far superior to the
VeraEdge in every way.
So, I took the time to mostly standardise my sensors which make things simpler to
manage/mantain.
As such, essentially I am only using 4 types of sensors:
...]]></summary>
<content type="html">&lt;p&gt;I finished migrating my &lt;a href=&quot;https://support.getvera.com/hc/en-us/articles/360021950353-Welcome-to-Vera-Getting-Started&quot;&gt;VeraEdge&lt;/a&gt; to &lt;a href=&quot;https://www.home-assistant.io/&quot;&gt;Home Assistant&lt;/a&gt;.  I think after
using it for some time, I find &lt;a href=&quot;https://www.home-assistant.io/&quot;&gt;Home Assistant&lt;/a&gt; far superior to the
&lt;a href=&quot;https://support.getvera.com/hc/en-us/articles/360021950353-Welcome-to-Vera-Getting-Started&quot;&gt;VeraEdge&lt;/a&gt; in every way.&lt;/p&gt;
&lt;p&gt;So, I took the time to mostly standardise my sensors which make things simpler to
manage/mantain.&lt;/p&gt;
&lt;p&gt;As such, essentially I am only using 4 types of sensors:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.robbshop.nl/neo-coolcam-raam-deur-sensor-z-wave-plus&quot;&gt;Neo Coolcam Door/Window Sensor DS01Z&lt;/a&gt; :
This is a door/window sensor.  I still have a couple of these sensors from before.
They are EOL now, being replaced by the DS07Z.&lt;br /&gt;
&lt;img src=&quot;/images/2023/sensor-ds01z.png&quot; alt=&quot;DS01Z&quot; /&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.robbshop.nl/neo-coolcam-raam-deursensor-z-wave-plus-met-usb-voeding&quot;&gt;New Coolcam Door/Window Sensor DS07Z&lt;/a&gt; :
This is a door/window sensor with integrated temperature and humidty sensors.  I
was standardirising to this sensor type for door/window and also for temperature
and humidity until they became hard to source.  The last couple of sensors of
this type I had to buy from AliExpress.  For the moment, I have enough sensors,
but in the future, I may need to find a new source.&lt;br /&gt;
&lt;img src=&quot;/images/2023/sensor-ds07z.png&quot; alt=&quot;DS07Z&quot; /&gt;&lt;br /&gt;
Since this sensor can be charged using a USB port, I have an additional sensor
to be used as a &amp;quot;charging&amp;quot; sensor.  That way I can replace batteries, and the
low batteries can be charged in the &amp;quot;charging&amp;quot; sensor.  This is due to the fact
usually the sensors are in hard to reach places, where USB power is not
easily available.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.robbshop.nl/fibaro-smoke-sensor-2-z-wave-plus&quot;&gt;Fibaro Smoke Sensor 2&lt;/a&gt; :
This is a Smoke Sensor with integrated temperature meter.  I am using this
to replace my &lt;strong&gt;&amp;quot;dumb&amp;quot;&lt;/strong&gt; smoke sensors.  These sensors only detect smoke
and do &lt;strong&gt;not&lt;/strong&gt; detect CO&lt;sub&gt;2&lt;/sub&gt;.  This is not a problem because on one hand&lt;sub&gt;2&lt;/sub&gt;se sensors are mounted on the ceiling&lt;sub&gt;2&lt;/sub&gt;ch is detrimental for CO&lt;sub&gt;2&lt;/sub&gt;
detection as CO&lt;sub&gt;2&lt;/sub&gt; tends to accumulate on the floor first.  The other
is that CO&lt;sub&gt;2&lt;/sub&gt; detection is more important for smokeless fires (i.e. gas
burning).  Since we are only using this for the water heater, it is less
of a priority.&lt;br /&gt;
&lt;img src=&quot;/images/2023/sensor-smoke.png&quot; alt=&quot;Smoke Sensor&quot; /&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.robbshop.nl/neo-coolcam-overstromingssensor-z-wave-plus-eol&quot;&gt;New Coolcam Water Leak sensor&lt;/a&gt; :
This is a water leak sensor.  Unfortunately is already EOL.&lt;br /&gt;
&lt;img src=&quot;/images/2023/sensor-leak.png&quot; alt=&quot;Water Leak Sensor&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I used to have other sensors types:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Philio Tech Door/Window Sensor&lt;/li&gt;
&lt;li&gt;Philio Tech multi sensors :
While these sensors were good in the sense that they paired easily with my
&lt;a href=&quot;https://support.getvera.com/hc/en-us/articles/360021950353-Welcome-to-Vera-Getting-Started&quot;&gt;VeraEdge&lt;/a&gt; and gave accurate reading, they were (at least to me)
not easy to open.  So every time I would want to replace the battery
I would &lt;strong&gt;accidentally&lt;/strong&gt; break the latches.&lt;/li&gt;
&lt;li&gt;Other door sensors :
Also, a number of sensors that I tried, made it difficult to stock up on
spare batteries.  Furthermore, specially for the door/window sensors,
some had awkward shapes.&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Happy New Year 2023</title>
<link href="https://www.0ink.net/posts/2023/2023-01-01-new-year.html"></link>
<id>urn:uuid:10f47ba9-ae4c-04c1-4f7b-c599df5b048e</id>
<updated>2023-01-11T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[
Best wishes for 2023!
This website is now 10 Years Old.
...]]></summary>
<content type="html">&lt;p&gt;&lt;img src=&quot;/images/2023/newyear-2023.png&quot; alt=&quot;NewYear2023&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Best wishes for 2023!&lt;/p&gt;
&lt;p&gt;This website is now &lt;strong&gt;10 Years Old&lt;/strong&gt;.&lt;/p&gt;</content>
</entry>
<entry>
<title>Home Assistant RFXCOM Integration</title>
<link href="https://www.0ink.net/posts/2022/2022-12-20-hassio-rfx.html"></link>
<id>urn:uuid:b7cd3709-4ebc-0c1c-cf7d-06508704abb0</id>
<updated>2022-12-15T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[RFXCOM RFXtrx
This integration is
to control RFXtrx devices.  I am using to control Somfy blinds and KlikAanKlikUit
remotes.

Add integration: RFXCOM RFXtrx
...]]></summary>
<content type="html">&lt;h3 id=&quot;RFXCOM+RFXtrx&quot; name=&quot;RFXCOM+RFXtrx&quot;&gt;RFXCOM RFXtrx&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://www.home-assistant.io/integrations/rfxtrx/&quot;&gt;This&lt;/a&gt; integration is
to control RFXtrx devices.  I am using to control Somfy blinds and KlikAanKlikUit
remotes.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add integration: &lt;code&gt;RFXCOM RFXtrx&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Conection type: &lt;code&gt;Serial&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Select device: &lt;code&gt;RFXtrx433XL - RFXtrx433XL, s/n: * - RFXCOM&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Remember to configure the RFXCOM unit &lt;strong&gt;before&lt;/strong&gt; using it with &lt;a href=&quot;https://www.home-assistant.io/&quot;&gt;Home Assistant&lt;/a&gt;
as &lt;a href=&quot;https://www.home-assistant.io/&quot;&gt;Home Assistant&lt;/a&gt; integration has limited control of it.&lt;/p&gt;
&lt;p&gt;Specifically, you need to use the RFXCOM &lt;a href=&quot;http://www.rfxcom.com/downloads.htm&quot;&gt;rfxmngr&lt;/a&gt;
tool to enable the relevant protocols and in the case of Somfy blinds, pair them.&lt;/p&gt;
&lt;p&gt;For my home I enabled the &lt;code&gt;AC&lt;/code&gt; protocol for use with
&lt;a href=&quot;https://klikaanklikuit.nl/&quot;&gt;KAKU&lt;/a&gt; devices.&lt;/p&gt;
&lt;h4 id=&quot;KlikAanKlikUit&quot; name=&quot;KlikAanKlikUit&quot;&gt;KlikAanKlikUit&lt;/h4&gt;
&lt;p&gt;&lt;a href=&quot;https://klikaanklikuit.nl/&quot;&gt;KAKU&lt;/a&gt; are inexpensive home automation devices that
use a wireless protocol very similar to X-10.&lt;/p&gt;
&lt;p&gt;The simplest way is to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;List integrations&lt;/li&gt;
&lt;li&gt;Configure &lt;code&gt;RFXTRX&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Enable Automatic Add&lt;/li&gt;
&lt;li&gt;Submit&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Afterwards, just use the lights and devices, and they will be added automatically.&lt;/p&gt;
&lt;h2 id=&quot;Somfy&quot; name=&quot;Somfy&quot;&gt;Somfy&lt;/h2&gt;
&lt;p&gt;For pairing the Somfy blinds, you can refer to &lt;a href=&quot;https://www.vlieshout.net/home-assistant-and-somfy-rts-with-rfxcom/&quot;&gt;this article&lt;/a&gt;
and &lt;a href=&quot;https://www.vesternet.com/en-eu/pages/apnt-79-controlling-somfy-rts-blinds-with-the-rfxtrx433e&quot;&gt;ths one&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In my configuration I am using:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;remoteID: 010E1 &amp;gt;  0 : 10 : E1 : 010E1
          123456
          ABCDEF
remoteID: 69121  &amp;gt; 0 : 16 : 225 : 4321
unitCode: 01

1234567890123456
071a000000000000
071a00000010e101&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;Tweaks&quot; name=&quot;Tweaks&quot;&gt;Tweaks&lt;/h2&gt;
&lt;p&gt;Because for some reason my Skylight cover says &amp;quot;Open&amp;quot; when is &amp;quot;Closed&amp;quot; and
viceversa.&lt;/p&gt;
&lt;p&gt;To clean that up we use the:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.home-assistant.io/integrations/cover.template/&quot;&gt;https://www.home-assistant.io/integrations/cover.template/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.home-assistant.io/integrations/cover/&quot;&gt;https://www.home-assistant.io/integrations/cover/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To create a &amp;quot;template&amp;quot; cover that shows things properly.  In &lt;code&gt;configuration.yaml&lt;/code&gt;
we have this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;cover:
  - platform: template
    covers:
      study_skylight_p:
        unique_id: 13d7a089-c536-4dc0-b2c0-e5dae6521460
        open_cover:
          service: cover.close_cover
          target:
            entity_id: cover.rfy_0010e1_1
        close_cover:
          service: cover.open_cover
          target:
            entity_id: cover.rfy_0010e1_1
        stop_cover:
          service: cover.stop_cover
          target:
            entity_id: cover.rfy_0010e1_1
&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>Home Assistant Behind Reverse Proxy</title>
<link href="https://www.0ink.net/posts/2022/2022-12-10-hassio-rev-proxy.html"></link>
<id>urn:uuid:d6baf888-30e6-7aed-f7ad-ff0daa5b78fa</id>
<updated>2022-12-15T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[To set-up a reverse proxy I took the following steps:

configure DNS
get Letsencrypt certificates
Configure NGINX
Configure Home Assistant to trust the proxy
...]]></summary>
<content type="html">&lt;p&gt;To set-up a reverse proxy I took the following steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;configure DNS&lt;/li&gt;
&lt;li&gt;get Letsencrypt certificates&lt;/li&gt;
&lt;li&gt;Configure NGINX&lt;/li&gt;
&lt;li&gt;Configure Home Assistant to trust the proxy&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At the time of this writing I can&#039;t really confirm if the reverse proxy
configuration for home assistant is working as I can&#039;t tell what IP
address is using for the &lt;code&gt;trustednetworks&lt;/code&gt; authenticator.&lt;/p&gt;
&lt;p&gt;Sample NGINX configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nginx&quot;&gt;
map $http_upgrade $connection_upgrade {
    default upgrade;
    &#039;&#039;      close;
}

server {
    # Update this line to be your domain
    server_name example.com;

    # These shouldn&#039;t need to be changed
    listen [::]:80 default_server ipv6only=off;
    return 301 https://$host$request_uri;
}

server {
    # Update this line to be your domain
    server_name example.com;

    # Ensure these lines point to your SSL certificate and key
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    # Use these lines instead if you created a self-signed certificate
    # ssl_certificate /etc/nginx/ssl/cert.pem;
    # ssl_certificate_key /etc/nginx/ssl/key.pem;

    # Ensure this line points to your dhparams file
    ssl_dhparam /etc/nginx/ssl/dhparams.pem;

    # These shouldn&#039;t need to be changed
    listen [::]:443 ssl default_server ipv6only=off; # if your nginx version is &amp;gt;= 1.9.5 you can also add the &quot;http2&quot; flag here
    add_header Strict-Transport-Security &quot;max-age=31536000; includeSubdomains&quot;;
    # ssl on; # Uncomment if you are using nginx &amp;lt; 1.15.0
    ssl_protocols TLSv1.2;
    ssl_ciphers &quot;EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4&quot;;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;

    proxy_buffering off;

    location / {
        proxy_pass http://127.0.0.1:8123;
        proxy_set_header Host $host;
        proxy_redirect http:// https://;
        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Home Assistant &lt;code&gt;configuration.yaml&lt;/code&gt; entries:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;http:
  # For extra security set this to only accept connections on localhost if NGINX is on the same machine
  # Uncommenting this will mean that you can only reach Home Assistant using the proxy, not directly via IP from other clients.
  # server_host: 127.0.0.1
  use_x_forwarded_for: true
  # You must set the trusted proxy IP address so that Home Assistant will properly accept connections
  # Set this to your NGINX machine IP, or localhost if hosted on the same machine.
  trusted_proxies: &amp;lt;NGINX IP address here, or 127.0.0.1 if hosted on the same machine&amp;gt;&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>Looking up docker image tags</title>
<link href="https://www.0ink.net/posts/2022/2022-12-01-docker-image-tags.html"></link>
<id>urn:uuid:e6d46bae-ffb6-b04e-328b-56a897e6d386</id>
<updated>2022-12-15T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This recipe is to check the tags defined for a specific Docker image
in docker.hub.
The basic API is at https://registry.hub.docker.com/v2
So the format is as follows:
https://registry.hub.docker.com/v2/repositories/{namespace}/{image}/tags/
Where:
...]]></summary>
<content type="html">&lt;p&gt;This recipe is to check the tags defined for a specific Docker image
in &lt;a href=&quot;https://hub.docker.com&quot;&gt;docker.hub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The basic API is at &lt;a href=&quot;https://registry.hub.docker.com/v2&quot;&gt;https://registry.hub.docker.com/v2&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So the format is as follows:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://registry.hub.docker.com/v2/repositories/&quot;&gt;https://registry.hub.docker.com/v2/repositories/&lt;/a&gt;&lt;/strong&gt;&lt;strong&gt;{namespace}&lt;/strong&gt;/&lt;strong&gt;{image}&lt;/strong&gt;&lt;strong&gt;/tags/&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;namespace&lt;/strong&gt; : usually is the user account posting the image.  For &lt;strong&gt;official&lt;/strong&gt; images
set the &lt;strong&gt;namespace&lt;/strong&gt; to &lt;code&gt;library&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;image&lt;/strong&gt; : Image name.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://hub.docker.com/_/alpine&quot;&gt;Docker Official alpine image&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;namespace: library&lt;/li&gt;
&lt;li&gt;image : alpine&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://hub.docker.com/r/photoprism/photoprism&quot;&gt;photoprism/photoprism&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;namespace : photoprism&lt;/li&gt;
&lt;li&gt;image : photoprism&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, you can then use &lt;code&gt;curl&lt;/code&gt; and &lt;code&gt;jq&lt;/code&gt; to access the relevant data.  For example,
to get the tags, last updated time and digest as a tsv:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -s -L $URL | jq -r &#039;(.results[] | select(.tag_status == &quot;active&quot;) | [.name, .last_updated
, .digest]) | @tsv&#039;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;v1+API&quot; name=&quot;v1+API&quot;&gt;v1 API&lt;/h2&gt;
&lt;p&gt;You could also use the v1 API while it is still available:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://registry.hub.docker.com/api/content/v1/repositories/public/&quot;&gt;https://registry.hub.docker.com/api/content/v1/repositories/public/&lt;/a&gt;&lt;/strong&gt;&lt;strong&gt;{namespace}&lt;/strong&gt;/&lt;strong&gt;{image}&lt;/strong&gt;&lt;strong&gt;/tags/&lt;/strong&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>Home Assistant Large Clock</title>
<link href="https://www.0ink.net/posts/2022/2022-11-21-hassio-large-clock.html"></link>
<id>urn:uuid:cdfc0d43-19b4-436a-6107-b7172c9ec759</id>
<updated>2024-02-02T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This recipe is my version of providing a &quot;large clock&quot; face in the
home assistant dashboard.
Enable serving local static files:

Create directory www in your config directory.
Restart home assistant.
...]]></summary>
<content type="html">&lt;p&gt;This recipe is my version of providing a &amp;quot;large clock&amp;quot; face in the
&lt;a href=&quot;https://home-assistant.io/&quot;&gt;home assistant&lt;/a&gt; dashboard.&lt;/p&gt;
&lt;p&gt;Enable serving local static files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create directory &lt;code&gt;www&lt;/code&gt; in your &lt;code&gt;config&lt;/code&gt; directory.&lt;/li&gt;
&lt;li&gt;Restart &lt;a href=&quot;https://home-assistant.io/&quot;&gt;home assistant&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Static files are now available as &lt;code&gt;http://homeassistant.local:8123/local/&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Place the HTML with your clock in a file i.e. &lt;code&gt;$config/www/clock.html&lt;/code&gt;.
I am using this:&lt;/p&gt;
&lt;script src=&quot;https://tortugalabs.github.io/embed-like-gist/embed.js?style=github&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showCopy=on&amp;amp;fetchFromJsDelivr=on&amp;amp;target=https://github.com/alejandroliu/0ink.net/blob/main/snippets/2022/hassio/clock.html&quot;&gt;&lt;/script&gt;
&lt;p&gt;Then add a &lt;a href=&quot;https://www.home-assistant.io/dashboards/iframe/&quot;&gt;webpage&lt;/a&gt; card:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;type: iframe
url: /local/clock.html
aspect_ratio: 45%&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Obviously you can fully exercise your HTML to get your clock to look
exactly like you want.&lt;/p&gt;
&lt;p&gt;I wrote this because I couldn&#039;t get the
&lt;a href=&quot;https://www.home-assistant.io/dashboards/markdown/&quot;&gt;markdown&lt;/a&gt; card
to style properly.  Also, I wasn&#039;t keen on installing the
&lt;a href=&quot;https://www.home-assistant.io/integrations/time_date/&quot;&gt;time and date&lt;/a&gt;
sensor which is required for the clock examples based on the
&lt;a href=&quot;https://www.home-assistant.io/dashboards/picture-elements/&quot;&gt;picture elements&lt;/a&gt;
card.&lt;/p&gt;
&lt;p&gt;Other implementations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://community.home-assistant.io/t/really-simple-big-clock/255971&quot;&gt;https://community.home-assistant.io/t/really-simple-big-clock/255971&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://community.home-assistant.io/t/just-a-big-clock/69976/2&quot;&gt;https://community.home-assistant.io/t/just-a-big-clock/69976/2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Home Assistant HTTP Based Authentication Backend</title>
<link href="https://www.0ink.net/posts/2022/2022-11-11-hassio-httpbasicauth.html"></link>
<id>urn:uuid:95bcddfa-0c2c-2ac9-12b3-2b2ac3dc359c</id>
<updated>2024-02-02T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This recipe is to authenticate users using a web server providing
Basic HTTP authentication
for it users.
This is useful if you want to consolidate users/passwords in a single
system.  So instead of managing users on Home Assistant you can
have all users managed from a central location.
...]]></summary>
<content type="html">&lt;p&gt;This recipe is to authenticate users using a web server providing
&lt;a href=&quot;https://en.wikipedia.org/wiki/Basic_access_authentication&quot;&gt;Basic HTTP authentication&lt;/a&gt;
for it users.&lt;/p&gt;
&lt;p&gt;This is useful if you want to consolidate users/passwords in a single
system.  So instead of managing users on &lt;a href=&quot;https://www.home-assistant.io/&quot;&gt;Home Assistant&lt;/a&gt; you can
have all users managed from a central location.&lt;/p&gt;
&lt;p&gt;It uses the &lt;a href=&quot;https://www.home-assistant.io/&quot;&gt;Home Assistant&lt;/a&gt;
&lt;a href=&quot;https://www.home-assistant.io/docs/authentication/providers/#command-line&quot;&gt;command line&lt;/a&gt;
authentication provider and the &lt;code&gt;curl&lt;/code&gt; command.&lt;/p&gt;
&lt;p&gt;To make it work is quite simple.  Copy this script to your &lt;code&gt;/config&lt;/code&gt;
directory as &lt;code&gt;curl_auth.sh&lt;/code&gt;:&lt;/p&gt;
&lt;script src=&quot;https://tortugalabs.github.io/embed-like-gist/embed.js?style=github&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showCopy=on&amp;amp;fetchFromJsDelivr=on&amp;amp;target=https://github.com/alejandroliu/0ink.net/blob/main/snippets/2022/hassio/curl_auth.sh&quot;&gt;&lt;/script&gt;
&lt;p&gt;Add the following lines to your &lt;code&gt;configure.yaml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;homeassistant:
  auth_providers:
    - type: command_line
      command: /config/curl_auth.sh
      args: [ &quot;http://your-web-site-url/&quot; ]
      meta: true&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make sure that you modify the URL in the configuration to a
web server that is doing Basic HTTP authentication.  It uses &lt;code&gt;curl&lt;/code&gt;
for checking URLs, so &lt;code&gt;http&lt;/code&gt; and &lt;code&gt;https&lt;/code&gt; protocols would work.&lt;/p&gt;
&lt;p&gt;If using &lt;code&gt;https&lt;/code&gt; with self-signed certificates, you need to pass the
&lt;code&gt;-k&lt;/code&gt; option which is then passed to &lt;code&gt;curl&lt;/code&gt;.  See
&lt;a href=&quot;https://man7.org/linux/man-pages/man1/curl.1.html&quot;&gt;curl(1)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;args: [ &quot;-k&quot;, &quot;https://your-web-site-url/&quot; ]&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>Moving to Home Assistant</title>
<link href="https://www.0ink.net/posts/2022/2022-11-01-hassio.html"></link>
<id>urn:uuid:267cd8ac-044f-0627-c4ab-2fdcbdb2c8e5</id>
<updated>2024-02-02T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[I am busy moving away from my VeraEdge
installation to a Home Assistant running on a Raspberry Pi 4.  This is
because it looks like the maker of the VeraEdge was bought and it is slowly being
phased out.
For this I am using the following parts:

...]]></summary>
<content type="html">&lt;p&gt;I am busy moving away from my &lt;a href=&quot;https://support.getvera.com/hc/en-us/articles/360021753434-VeraEdge-Getting-Started-How-To&quot;&gt;VeraEdge&lt;/a&gt;
installation to a &lt;a href=&quot;https://www.home-assistant.io/&quot;&gt;Home Assistant&lt;/a&gt; running on a &lt;a href=&quot;https://www.raspberrypi.com/products/raspberry-pi-4-model-b/&quot;&gt;Raspberry Pi 4&lt;/a&gt;.  This is
because it looks like the maker of the VeraEdge was bought and it is slowly being
phased out.&lt;/p&gt;
&lt;p&gt;For this I am using the following parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://wiki.geekworm.com/X728&quot;&gt;Geekwork X728 18650 UPS + X728-C1 case&lt;/a&gt; : This
provides with a case (with cooling fan), UPS and RTC functionality.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://aeotec.com/products/aeotec-z-stick-gen5/&quot;&gt;Aeotec Z-Stick Gen5+&lt;/a&gt; : For
Z-Wave compatibility.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://phoscon.de/en/conbee2&quot;&gt;ConBee 2&lt;/a&gt; : For ZigBee compatibility.&lt;/li&gt;
&lt;li&gt;A Raspberry Pi 4 - 4GB&lt;/li&gt;
&lt;li&gt;64GB SD Card.  Actually I wanted to use a 32GB SD card, but the 64GB had better
specs and was only a couple os bucks more expensive.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I will be reusing these components:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.robbshop.nl/slimme-meter-kabel-usb-p1-1-meter&quot;&gt;Smart Meter USB-P1 cable&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.rfxcom.com/RFXtrx433XL-USB-43392MHz-Transceiver&quot;&gt;RFXtrx433XL USB HA controller&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Hardware+build&quot; name=&quot;Hardware+build&quot;&gt;Hardware build&lt;/h2&gt;
&lt;p&gt;Building the case is simple and straight forward.  You can follow this
video on &lt;a href=&quot;https://www.youtube.com/watch?v=QOG30LXb6ds&quot;&gt;youtube&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The steps are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Open the case.&lt;/li&gt;
&lt;li&gt;Install the fan.&lt;/li&gt;
&lt;li&gt;Install the additional battery holder.&lt;/li&gt;
&lt;li&gt;Install the power button.&lt;/li&gt;
&lt;li&gt;Screw the spacers to the Raspberry Pi.&lt;/li&gt;
&lt;li&gt;Insert the X728 UPS hat on top and screw in place.&lt;/li&gt;
&lt;li&gt;Install batteries.&lt;/li&gt;
&lt;li&gt;Plug connectors.&lt;/li&gt;
&lt;li&gt;Screw the Raspberry Pi to the case.&lt;/li&gt;
&lt;li&gt;Test that everything is in working order.&lt;/li&gt;
&lt;li&gt;Optional: Set the jumper selector to auto power-on.&lt;/li&gt;
&lt;li&gt;Close the case.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Case+software&quot; name=&quot;Case+software&quot;&gt;Case software&lt;/h2&gt;
&lt;p&gt;I did not like the software that comes with the case, so I rolled-up
my own.  The RTC uses the standard &lt;code&gt;rtc-ds1307&lt;/code&gt; which is in the Linux
kernel.  For GPIO programming I am using the &lt;code&gt;/sysfs&lt;/code&gt; interface.  The
only component that requires &lt;em&gt;custom&lt;/em&gt; programming was the battery
charge and voltage readings.  For that I wrote a small C program.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alejandroliu/0ink.net/tree/main/snippets/2022/X728/src&quot;&gt;x728batt&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is tied to &lt;code&gt;systemd&lt;/code&gt; through these files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/snippets/2022/X728/x728clock.service&quot;&gt;/etc/systemd/system/x728clock.service&lt;/a&gt;
executes &lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/snippets/2022/X728/scripts/clock.sh&quot;&gt;clock.sh&lt;/a&gt;
This is used to load the RTC kernel modules and activate the RTC in the
i&lt;sup&gt;2&lt;/sup&gt;c bus.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/snippets/2022/X728/x728ups.service&quot;&gt;/etc/systemd/system/x728ups.service&lt;/a&gt;
executes &lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/snippets/2022/X728/scripts/upsmon.sh&quot;&gt;upsmon.sh&lt;/a&gt;
This is used to monitor the Push Button, the A/C power status and if the
A/C power is lost, the battery status.  It will trigger a graceful shutdown
if the button is pressed or if the battery charge is insufficient.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/snippets/2022/X728/gpio-poweroff&quot;&gt;/lib/systemd/system-shutdown/gpio-poweroff&lt;/a&gt;
This is used to turn off the UPS power if the user issues the &lt;code&gt;poweroff&lt;/code&gt;
command.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In addition to this, I created a script to inject the &lt;a href=&quot;https://www.home-assistant.io/&quot;&gt;Home Assistant&lt;/a&gt;
Raspberry Pi image with the relevant files and also adds a
&lt;a href=&quot;https://rauc.readthedocs.io/en/latest/reference.html#system-configuration-file&quot;&gt;RAUC post-install handler&lt;/a&gt;.
so that OTA upgrades will keep these customizations.&lt;/p&gt;
&lt;p&gt;As a bonus I am adding a &lt;a href=&quot;https://github.com/TortugaLabs/muninlite&quot;&gt;munin-node&lt;/a&gt;
systemd unit for system monitoring.  And yes, I am old-fashioned.&lt;/p&gt;
&lt;p&gt;Also, I am enabling &lt;code&gt;ssh&lt;/code&gt; to the underlying Operating System.&lt;/p&gt;
&lt;h2 id=&quot;Home+Assistant+installation&quot; name=&quot;Home+Assistant+installation&quot;&gt;Home Assistant installation&lt;/h2&gt;
&lt;p&gt;Following the raspberry pi installation &lt;a href=&quot;https://www.home-assistant.io/installation/raspberrypi/&quot;&gt;guide&lt;/a&gt;
is quite straight forward.&lt;/p&gt;
&lt;p&gt;I chose to use the HAOS image install as it gives a more &lt;em&gt;consumer device&lt;/em&gt;
experience.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Download the 64bit image for Raspberry Pi 4 from the
&lt;a href=&quot;https://github.com/home-assistant/operating-system/releases&quot;&gt;releases page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Use the &lt;a href=&quot;https://github.com/alejandroliu/0ink.net/tree/main/snippets/2022/X728/OTA&quot;&gt;haos-x728.sh&lt;/a&gt;
to customize the image to include my X728 files.&lt;/li&gt;
&lt;li&gt;Write the modified image to SD card.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Boot the raspberry pi from the new SD card and do the GUI installation.
For my case, I needed to login to my router to look-up the IP address.
It is configured via DHCP.  I also statically assign an IP address and DNS
name based on MAC address.  To make sure that the DNS name and the host name
match, I modified it on the configuration:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Settings&lt;/code&gt; -&amp;gt; &lt;code&gt;System&lt;/code&gt; -&amp;gt; &lt;code&gt;network&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Modify &lt;code&gt;hostname&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;Initial+configuration&quot; name=&quot;Initial+configuration&quot;&gt;Initial configuration&lt;/h2&gt;
&lt;p&gt;Set up home areas.  I set-up one area per room, following a naming convention:&lt;/p&gt;
&lt;p&gt;F &lt;strong&gt;floor-number&lt;/strong&gt; &lt;strong&gt;Room-name&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;F0 Kitchen&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;F2 Attic&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Also, create additional areas for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;External&lt;/code&gt; : External items&lt;/li&gt;
&lt;li&gt;&lt;code&gt;System&lt;/code&gt; : System related entities and devices&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For devices, I am using this naming convention:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Room-name&lt;/strong&gt; &lt;strong&gt;room-section&lt;/strong&gt; &lt;strong&gt;device&lt;/strong&gt; &lt;strong&gt;optional&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The idea is to make it simple to guess the name for voice recognition.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Room-name&lt;/strong&gt; : this should match the area.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;room-section&lt;/strong&gt; : &lt;strong&gt;optional&lt;/strong&gt;, section of the room this applies to.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;device&lt;/strong&gt; : device type.
&lt;ul&gt;
&lt;li&gt;Chromecast : google chromecast device&lt;/li&gt;
&lt;li&gt;Display : google nest hub&lt;/li&gt;
&lt;li&gt;TV : with optional &lt;strong&gt;casting&lt;/strong&gt;, &lt;strong&gt;upnp&lt;/strong&gt; or &lt;strong&gt;api&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Skylight&lt;/li&gt;
&lt;li&gt;Light&lt;/li&gt;
&lt;li&gt;Switch&lt;/li&gt;
&lt;li&gt;Double Switch&lt;/li&gt;
&lt;li&gt;Remote: Remote control&lt;/li&gt;
&lt;li&gt;Window Sensor&lt;/li&gt;
&lt;li&gt;Door Sensor&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;optional&lt;/strong&gt; : used for when multiple device of the same type are in the same room.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Add-Ons&quot; name=&quot;Add-Ons&quot;&gt;Add-Ons&lt;/h2&gt;
&lt;p&gt;I installed the followng add-ons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Home Assistant Community Add-ons
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/hassio-addons/addon-vscode&quot;&gt;Studio Code Server&lt;/a&gt; : for
editing files.  Press &lt;kbd&gt;F1&lt;/kbd&gt; and start typing &lt;code&gt;home assistant&lt;/code&gt; to view
available integration commands.  This is needed because (unfortunately)
not everything can be configured through the UI.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/hassio-addons/addon-zwave-js-ui&quot;&gt;Z-Wave JS UI&lt;/a&gt; : Instead
of &lt;strong&gt;Official add-ons&lt;/strong&gt;.  The community add-on gives you a control panel with
more detailed control featires.  Specifically you can set group associations.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Also, adding my own repository: &lt;a href=&quot;https://github.com/iliu-net/hassio-addons&quot;&gt;https://github.com/iliu-net/hassio-addons&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;rsync-folders : save data and backups to remote server using rsync&lt;/li&gt;
&lt;li&gt;watchdogdev : watchdog timer&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Further+Configuration&quot; name=&quot;Further+Configuration&quot;&gt;Further Configuration&lt;/h2&gt;
&lt;p&gt;These configurations require modifying files.  So usually I would do them
&lt;strong&gt;after&lt;/strong&gt; installing &lt;code&gt;Studio Code Server&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;Modifying+authentications&quot; name=&quot;Modifying+authentications&quot;&gt;Modifying authentications&lt;/h3&gt;
&lt;p&gt;To simplify login in local networks (specially to support physical
control panels) I configured the
&lt;a href=&quot;https://www.home-assistant.io/docs/authentication/providers/#trusted-networks&quot;&gt;trusted_networks&lt;/a&gt;
by adding the following lines to your &lt;code&gt;configuration.yaml&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;homeassistant:
  auth_providers:
    - type: trusted_networks
      trusted_networks:
      - 192.168.2.0/24&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Essentially what this does is that devices connecting from the
&lt;em&gt;trusted networks&lt;/em&gt; do not to login with username/password.&lt;/p&gt;
&lt;h3 id=&quot;System+temperature&quot; name=&quot;System+temperature&quot;&gt;System temperature&lt;/h3&gt;
&lt;p&gt;For fun I also configured a CPU temperature sensor.  Add this
to &lt;code&gt;configuration.yaml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;sensor:
  ### command line
  - platform: command_line
    name: CPU Temperature
    command: &quot;cat /sys/class/thermal/thermal_zone0/temp&quot; # RPi
    # command: &quot;cat /sys/class/thermal/thermal_zone2/temp&quot; # NUC
    # If errors occur, remove degree symbol below
    unit_of_measurement: &quot;°C&quot;
    value_template: &quot;{{ &#039;%.1f&#039; | format(value | multiply(0.001)) }}&quot; # RPi &amp;amp; NUC
    unique_id: sys_cpu_temp&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Integrations&quot; name=&quot;Integrations&quot;&gt;Integrations&lt;/h2&gt;
&lt;h3 id=&quot;DSMR+Slimme+Meter&quot; name=&quot;DSMR+Slimme+Meter&quot;&gt;DSMR Slimme Meter&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://www.home-assistant.io/integrations/dsmr&quot;&gt;This&lt;/a&gt; integration is to read
energy consumption as provided by NL smart meters.  Just make sure that
you get the right cable.  I am using a cable from
&lt;a href=&quot;https://www.robbshop.nl/slimme-meter-kabel-usb-p1-1-meter&quot;&gt;ROBBshop&lt;/a&gt;.  Just
plug and add to the integrations.&lt;/p&gt;
&lt;p&gt;To include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Add Integration&lt;/code&gt; : &lt;code&gt;DSMR Slimme Meter&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Serial&lt;/code&gt; : connection&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Select device&lt;/code&gt;: Select the right serial port  (should be easy to identify).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DSMR Version&lt;/code&gt; : &lt;code&gt;5&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is very easy to add and configure.&lt;/p&gt;
&lt;h3 id=&quot;ZigBee+Home+Automation&quot; name=&quot;ZigBee+Home+Automation&quot;&gt;ZigBee Home Automation&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://www.home-assistant.io/integrations/zha&quot;&gt;This&lt;/a&gt; integration is automatically
discovered for supported coordinators.  I am using
a &lt;a href=&quot;https://phoscon.de/en/conbee2&quot;&gt;ConBee II&lt;/a&gt; ZigBee coordinator.  As long as the
device is supported (see
&lt;a href=&quot;https://zigbee.blakadder.com/index.html&quot;&gt;compatibility list&lt;/a&gt; ) things are fairly
easy and simple.&lt;/p&gt;
&lt;p&gt;There are multiple options for ZigBee support.  I opted for ZHA because it has
fairly good device support and is easy to use and set-up.&lt;/p&gt;
&lt;p&gt;The alternatives are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.zigbee2mqtt.io/&quot;&gt;Zibgee2MQTT&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;Good for power users, execellent configuratbility and the best device support&lt;/li&gt;
&lt;li&gt;It can be complicated.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dresden-elektronik.github.io/deconz-rest-doc/&quot;&gt;deCONZ Add-On&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;Made by the ConBee2 developers.  There are no real benefits to using
this integration.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Z-Wave+automation&quot; name=&quot;Z-Wave+automation&quot;&gt;Z-Wave automation&lt;/h3&gt;
&lt;p&gt;For Z-Wave I am using the
&lt;a href=&quot;https://www.home-assistant.io/integrations/zwave_js&quot;&gt;Z-Wave JS&lt;/a&gt;
integration paired with a
&lt;a href=&quot;https://aeotec.com/products/aeotec-z-stick-gen5/&quot;&gt;Aeotec Z-Stick Gen5&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I am using the
&lt;a href=&quot;https://github.com/hassio-addons/addon-zwave-js-ui&quot;&gt;Z-Wave JS UI&lt;/a&gt;
from Community Add-ons because that gives you a control panel
user interface that is handy when debugging obscure Z-Wave issues and
also has support for creating direct node group associations.&lt;/p&gt;
&lt;p&gt;In most day to day situations, I don&#039;t really use this control panel, as
most operations can be done from the &lt;a href=&quot;https://www.home-assistant.io/&quot;&gt;Home Assistant&lt;/a&gt; integration
directly.&lt;/p&gt;
&lt;p&gt;When adding this, it is &lt;em&gt;not&lt;/em&gt; possible to do it from the auto-discovered
&lt;em&gt;z-stick gen5&lt;/em&gt; as that will automatically install the core Z-Wave JS add-on.
So just ignore it and use the &lt;code&gt;Add Integration&lt;/code&gt; functionality and pick
&lt;code&gt;Z-Wave JS&lt;/code&gt; integration from the menu.  This will let you skip the
standard add-on installation and let you specify the right Z-Wave JS UI add-on
instead.&lt;/p&gt;
&lt;h3 id=&quot;Other+integrations&quot; name=&quot;Other+integrations&quot;&gt;Other integrations&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.home-assistant.io/integrations/buienradar&quot;&gt;Buienradar&lt;/a&gt; : Dutch
weather data.  Just add it and it works mostly out of the box.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.home-assistant.io/integrations/dlna_dms&quot;&gt;DLNA media servers&lt;/a&gt; :
these are discovered automatically as long as they are in the same Subnet.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.home-assistant.io/integrations/cast&quot;&gt;Google Cast&lt;/a&gt; :
These are also discovered automatically.  Used for Android TV devices and
Google Nest speakers/displays.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.home-assistant.io/integrations/ipp&quot;&gt;Printer&lt;/a&gt; : This was
automatically discovered.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.home-assistant.io/integrations/waze_travel_time&quot;&gt;Waze Travel Time&lt;/a&gt; :
Show the commute time between two points.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.home-assistant.io/integrations/rdw&quot;&gt;Netherlands Vehicle Authority&lt;/a&gt; :
Yes, this can be done, but not sure its use.  Maybe to remind you that the APK
is due?&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.home-assistant.io/integrations/philips_js/&quot;&gt;Philips TV&lt;/a&gt; :
Installing this integration is quite straight forward.  This enables automation
options, but I don&#039;t know what to do with it yet.  Also has the limitation that
it can&#039;t be turned on using the API.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.home-assistant.io/integrations/jellyfin/&quot;&gt;Jellyfin&lt;/a&gt; :
Add a Jellyfin server as a media source.  Note that only a single Jellyfin
can be configured.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;References&quot; name=&quot;References&quot;&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/home-assistant/operating-system&quot;&gt;https://github.com/home-assistant/operating-system&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.home-assistant.io/docs/operating-system/getting-started&quot;&gt;https://developers.home-assistant.io/docs/operating-system/getting-started&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Markdown cheat sheet</title>
<link href="https://www.0ink.net/posts/2022/2022-10-20-md-cheatsheet.html"></link>
<id>urn:uuid:68871fe4-8755-c471-09dd-0a255f60a766</id>
<updated>2022-10-12T00:00:00+02:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This is intended as a quick reference and showcase. For more complete info,
see John Gruber's original spec
and the Github-flavored Markdown info page.
Headers
Source:

...]]></summary>
<content type="html">&lt;p&gt;This is intended as a quick reference and showcase. For more complete info,
see &lt;a href=&quot;http://daringfireball.net/projects/markdown/&quot;&gt;John Gruber&#039;s original spec&lt;/a&gt;
and the &lt;a href=&quot;http://github.github.com/github-flavored-markdown/&quot;&gt;Github-flavored Markdown info page&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;Headers&quot; name=&quot;Headers&quot;&gt;Headers&lt;/h2&gt;
&lt;p&gt;Source:&lt;/p&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;# H1
## H2
### H3
#### H4
##### H5
###### H6

Alternatively, for H1 and H2, an underline-ish style:

Alt-H1
======

Alt-H2
------&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;Output:&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;H1&quot; name=&quot;H1&quot;&gt;H1&lt;/h2&gt;
&lt;h3 id=&quot;H2&quot; name=&quot;H2&quot;&gt;H2&lt;/h3&gt;
&lt;h4 id=&quot;H3&quot; name=&quot;H3&quot;&gt;H3&lt;/h4&gt;
&lt;h5 id=&quot;H4&quot; name=&quot;H4&quot;&gt;H4&lt;/h5&gt;
&lt;h6 id=&quot;H5&quot; name=&quot;H5&quot;&gt;H5&lt;/h6&gt;
&lt;h7 id=&quot;H6&quot; name=&quot;H6&quot;&gt;H6&lt;/h7&gt;
&lt;p&gt;Alternatively, for H1 and H2, an underline-ish style:&lt;/p&gt;
&lt;h1&gt;Alt-H1&lt;/h1&gt;
&lt;h2&gt;Alt-H2&lt;/h2&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;Emphasis&quot; name=&quot;Emphasis&quot;&gt;Emphasis&lt;/h2&gt;
&lt;p&gt;Source:&lt;/p&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;Emphasis, aka italics, with *asterisks* or _underscores_.

Strong emphasis, aka bold, with **asterisks** or __underscores__.

Combined emphasis with **asterisks and _underscores_**.

Strikethrough uses two tildes. ~~Scratch this.~~&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;Output:&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Emphasis, aka italics, with &lt;em&gt;asterisks&lt;/em&gt; or &lt;em&gt;underscores&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Strong emphasis, aka bold, with &lt;strong&gt;asterisks&lt;/strong&gt; or &lt;strong&gt;underscores&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Combined emphasis with &lt;strong&gt;asterisks and &lt;em&gt;underscores&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Strikethrough uses two tildes. &lt;del&gt;Scratch this.&lt;/del&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;Lists&quot; name=&quot;Lists&quot;&gt;Lists&lt;/h2&gt;
&lt;p&gt;(In this example, leading and trailing spaces are shown with with dots: ⋅)&lt;/p&gt;
&lt;p&gt;Source:&lt;/p&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;1. First ordered list item
2. Another item
⋅⋅* Unordered sub-list.
1. Actual numbers don&#039;t matter, just that it&#039;s a number
⋅⋅1. Ordered sub-list
4. And another item.

⋅⋅⋅You can have properly indented paragraphs within list items. Notice the blank line above, and the leading spaces (at least one, but we&#039;ll use three here to also align the raw Markdown).

⋅⋅⋅To have a line break without a paragraph, you will need to use two trailing spaces.⋅⋅
⋅⋅⋅Note that this line is separate, but within the same paragraph.⋅⋅
⋅⋅⋅(This is contrary to the typical GFM line break behaviour, where trailing spaces are not required.)

* Unordered list can use asterisks
- Or minuses
+ Or pluses&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;Output:&lt;/p&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li&gt;First ordered list item&lt;/li&gt;
&lt;li&gt;Another item&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Unordered sub-list.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;Actual numbers don&#039;t matter, just that it&#039;s a number&lt;/li&gt;
&lt;li&gt;Ordered sub-list&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;And another item.&lt;/p&gt;
&lt;p&gt;You can have properly indented paragraphs within list items. Notice the blank line above, and the leading spaces (at least one, but we&#039;ll use three here to also align the raw Markdown).&lt;/p&gt;
&lt;p&gt;To have a line break without a paragraph, you will need to use two trailing spaces.
Note that this line is separate, but within the same paragraph.
(This is contrary to the typical GFM line break behaviour, where trailing spaces are not required.)&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Unordered list can use asterisks&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;Or minuses&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;Or pluses&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;Links&quot; name=&quot;Links&quot;&gt;Links&lt;/h2&gt;
&lt;p&gt;There are two ways to create links.&lt;/p&gt;
&lt;p&gt;Source:&lt;/p&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;[I&#039;m an inline-style link](https://www.google.com)

[I&#039;m an inline-style link with title](https://www.google.com &quot;Google&#039;s Homepage&quot;)

[I&#039;m a reference-style link][Arbitrary case-insensitive reference text]

[I&#039;m a relative reference to a repository file](../blob/master/LICENSE)

[You can use numbers for reference-style link definitions][1]

Or leave it empty and use the [link text itself].

URLs and URLs in angle brackets will automatically get turned into links.
http://www.example.com or &amp;lt;http://www.example.com&amp;gt; and sometimes
example.com (but not on Github, for example).

Some text to show that the reference links can follow later.

[arbitrary case-insensitive reference text]: https://www.mozilla.org
[1]: http://slashdot.org
[link text itself]: http://www.reddit.com&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;Output:&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;a href=&quot;https://www.google.com&quot;&gt;I&#039;m an inline-style link&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.google.com&quot; title=&quot;Google&amp;#039;s Homepage&quot;&gt;I&#039;m an inline-style link with title&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.mozilla.org&quot;&gt;I&#039;m a reference-style link&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;../blob/master/LICENSE&quot;&gt;I&#039;m a relative reference to a repository file&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://slashdot.org&quot;&gt;You can use numbers for reference-style link definitions&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Or leave it empty and use the &lt;a href=&quot;http://www.reddit.com&quot;&gt;link text itself&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;URLs and URLs in angle brackets will automatically get turned into links.
&lt;a href=&quot;http://www.example.com&quot;&gt;http://www.example.com&lt;/a&gt; or &lt;a href=&quot;http://www.example.com&quot;&gt;http://www.example.com&lt;/a&gt; and sometimes
example.com (but not on Github, for example).&lt;/p&gt;
&lt;p&gt;Some text to show that the reference links can follow later.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;Images&quot; name=&quot;Images&quot;&gt;Images&lt;/h2&gt;
&lt;p&gt;Source:&lt;/p&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;Here&#039;s our logo (hover to see the title text):

Inline-style:
![alt text](https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png &quot;Logo Title Text 1&quot;)

Reference-style:
![alt text][logo]

[logo]: https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png &quot;Logo Title Text 2&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;Output:&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Here&#039;s our logo (hover to see the title text):&lt;/p&gt;
&lt;p&gt;Inline-style:
&lt;img src=&quot;https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png&quot; alt=&quot;alt text&quot; title=&quot;Logo Title Text 1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Reference-style:
&lt;img src=&quot;https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png&quot; alt=&quot;alt text&quot; title=&quot;Logo Title Text 2&quot; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;Code+and+Syntax+Highlighting&quot; name=&quot;Code+and+Syntax+Highlighting&quot;&gt;Code and Syntax Highlighting&lt;/h2&gt;
&lt;p&gt;Code blocks are part of the Markdown spec, but syntax highlighting isn&#039;t.
However, many renderers support syntax highlighting. Which languages are supported
and how those language names should be written will vary from renderer to renderer.&lt;/p&gt;
&lt;p&gt;To see the complete list, and how to write the language names, see the
&lt;a href=&quot;http://softwaremaniacs.org/media/soft/highlight/test.html&quot;&gt;highlight.js demo page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Source:&lt;/p&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;Inline `code` has `back-ticks around` it.&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;Output:&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Inline &lt;code&gt;code&lt;/code&gt; has &lt;code&gt;back-ticks around&lt;/code&gt; it.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Blocks of code are either fenced by lines with three back-ticks
&amp;quot;```&amp;quot;,
or are indented with four spaces. I recommend only using the fenced code
blocks -- they&#039;re easier and only they support syntax highlighting.&lt;/p&gt;
&lt;p&gt;Source:&lt;/p&gt;
&lt;hr /&gt;
&lt;pre lang=&quot;no-highlight&quot;&gt;&lt;code&gt;```javascript
var s = &quot;JavaScript syntax highlighting&quot;;
alert(s);
```

```python
s = &quot;Python syntax highlighting&quot;
print s
```

```
No language indicated, so no syntax highlighting.
But let&#039;s throw in a &amp;lt;b&amp;gt;tag&amp;lt;/b&amp;gt;.
```
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;Output:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;var s = &quot;JavaScript syntax highlighting&quot;;
alert(s);&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;s = &quot;Python syntax highlighting&quot;
print s&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;No language indicated, so no syntax highlighting in Markdown Here (varies on Github).
But let&#039;s throw in a &amp;lt;b&amp;gt;tag&amp;lt;/b&amp;gt;.&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;Tables&quot; name=&quot;Tables&quot;&gt;Tables&lt;/h2&gt;
&lt;p&gt;Tables aren&#039;t part of the core Markdown spec, but they are part of GFM. They
are an easy way of adding tables to your email -- a task that would otherwise
require copy-pasting from another application.&lt;/p&gt;
&lt;p&gt;Source:&lt;/p&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;Colons can be used to align columns.

| Tables        | Are           | Cool  |
| ------------- |:-------------:| -----:|
| col 3 is      | right-aligned | $1600 |
| col 2 is      | centered      |   $12 |
| zebra stripes | are neat      |    $1 |

There must be at least 3 dashes separating each header cell.
The outer pipes (|) are optional, and you don&#039;t need to make the
raw Markdown line up prettily. You can also use inline Markdown.

Markdown | Less | Pretty
--- | --- | ---
*Still* | `renders` | **nicely**
1 | 2 | 3&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;Output:&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Colons can be used to align columns.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tables&lt;/th&gt;
&lt;th style=&quot;text-align: center;&quot;&gt;Are&lt;/th&gt;
&lt;th style=&quot;text-align: right;&quot;&gt;Cool&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;col 3 is&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;right-aligned&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;$1600&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;col 2 is&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;centered&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;$12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;zebra stripes&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;are neat&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;$1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;There must be at least 3 dashes separating each header cell. The outer pipes (|)
are optional, and you don&#039;t need to make the raw Markdown line up prettily. You
can also use inline Markdown.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Markdown&lt;/th&gt;
&lt;th&gt;Less&lt;/th&gt;
&lt;th&gt;Pretty&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Still&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;renders&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;nicely&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;Blockquotes&quot; name=&quot;Blockquotes&quot;&gt;Blockquotes&lt;/h2&gt;
&lt;p&gt;Source:&lt;/p&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;&amp;gt; Blockquotes are very handy in email to emulate reply text.
&amp;gt; This line is part of the same quote.

Quote break.

&amp;gt; This is a very long line that will still be quoted properly when it wraps. Oh boy let&#039;s keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote.&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;Output:&lt;/p&gt;
&lt;hr /&gt;
&lt;blockquote&gt;
&lt;p&gt;Blockquotes are very handy in email to emulate reply text.
This line is part of the same quote.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Quote break.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This is a very long line that will still be quoted properly when it wraps. Oh boy let&#039;s keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can &lt;em&gt;put&lt;/em&gt; &lt;strong&gt;Markdown&lt;/strong&gt; into a blockquote.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;Inline+HTML&quot; name=&quot;Inline+HTML&quot;&gt;Inline HTML&lt;/h2&gt;
&lt;p&gt;You can also use raw HTML in your Markdown, and it&#039;ll mostly work pretty well.&lt;/p&gt;
&lt;p&gt;Source:&lt;/p&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;&amp;lt;dl&amp;gt;
  &amp;lt;dt&amp;gt;Definition list&amp;lt;/dt&amp;gt;
  &amp;lt;dd&amp;gt;Is something people use sometimes.&amp;lt;/dd&amp;gt;

  &amp;lt;dt&amp;gt;Markdown in HTML&amp;lt;/dt&amp;gt;
  &amp;lt;dd&amp;gt;Does *not* work **very** well. Use HTML &amp;lt;em&amp;gt;tags&amp;lt;/em&amp;gt;.&amp;lt;/dd&amp;gt;
&amp;lt;/dl&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;Output:&lt;/p&gt;
&lt;hr /&gt;
&lt;dl&gt;
  &lt;dt&gt;Definition list&lt;/dt&gt;
  &lt;dd&gt;Is something people use sometimes.&lt;/dd&gt;

  &lt;dt&gt;Markdown in HTML&lt;/dt&gt;
  &lt;dd&gt;Does *not* work **very** well. Use HTML &lt;em&gt;tags&lt;/em&gt;.&lt;/dd&gt;
&lt;/dl&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;Horizontal+Rule&quot; name=&quot;Horizontal+Rule&quot;&gt;Horizontal Rule&lt;/h2&gt;
&lt;p&gt;Source:&lt;/p&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;Three or more...

---

Hyphens

***

Asterisks

___

Underscores&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;Output:&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Three or more...&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Hyphens&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Asterisks&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Underscores&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;Line+Breaks&quot; name=&quot;Line+Breaks&quot;&gt;Line Breaks&lt;/h2&gt;
&lt;p&gt;My basic recommendation for learning how line breaks work is to experiment and
discover -- hit &amp;lt;Enter&amp;gt; once (i.e., insert one newline), then hit it twice
(i.e., insert two newlines), see what happens. You&#039;ll soon learn to get what you
want. &amp;quot;Markdown Toggle&amp;quot; is your friend.&lt;/p&gt;
&lt;p&gt;Here are some things to try out:&lt;/p&gt;
&lt;p&gt;Source:&lt;/p&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;Here&#039;s a line for us to start with.

This line is separated from the one above by two newlines, so it will be a *separate paragraph*.

This line is also a separate paragraph, but...
This line is only separated by a single newline, so it&#039;s a separate line in the *same paragraph*.&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;Output:&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Here&#039;s a line for us to start with.&lt;/p&gt;
&lt;p&gt;This line is separated from the one above by two newlines, so it will be a &lt;em&gt;separate paragraph&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This line is also begins a separate paragraph, but...
This line is only separated by a single newline, so it&#039;s a separate line in the &lt;em&gt;same paragraph&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;(Technical note: &lt;em&gt;Markdown Here&lt;/em&gt; uses GFM line breaks, so there&#039;s no need to use MD&#039;s two-space line breaks.)&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;Local+extensions&quot; name=&quot;Local+extensions&quot;&gt;Local extensions&lt;/h2&gt;
&lt;p&gt;Source:&lt;/p&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;- [x] ticked checkboxes
- [ ] unticked check box
- Use ++insert++ text
- This is ^^superscript^^ stuff.
- This is ,,subscript,, stuff.
- This is ~~striketrough~~ text.
- This is ??marked?? text.
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;Output:&lt;/p&gt;
&lt;hr /&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; disabled checked&gt; ticked checkboxes&lt;/li&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; disabled &gt; unticked check box&lt;/li&gt;
&lt;li&gt;Use &lt;ins&gt;insert&lt;/ins&gt; text&lt;/li&gt;
&lt;li&gt;This is &lt;sup&gt;superscript&lt;/sup&gt; stuff.&lt;/li&gt;
&lt;li&gt;This is &lt;sub&gt;subscript&lt;/sub&gt; stuff.&lt;/li&gt;
&lt;li&gt;This is &lt;del&gt;striketrough&lt;/del&gt; text.&lt;/li&gt;
&lt;li&gt;This is &lt;mark&gt;marked&lt;/mark&gt; text.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;Diagrams&quot; name=&quot;Diagrams&quot;&gt;Diagrams&lt;/h2&gt;
&lt;p&gt;Source:&lt;/p&gt;
&lt;hr /&gt;
&lt;pre lang=&quot;no-highlight&quot;&gt;&lt;code&gt;dot {
  graph NET {
    layout=neato

    edge [weight=2.0 fontsize=7]
    node [style=filled shape=box]

    node [fillcolor=white] kpnmodem
    node [fillcolor=lightblue] ngs1 ngs2 ngs3
    node [fillcolor=lightgreen] cctv_sw
    node [fillcolor=silver] cn4 iptv1 veraedge1 nd2 nd3 philtv
    node [fillcolor=yellow] wac1 wac2 wac3 owap1

    kpnmodem -- cn4 [label=&quot;v2&quot; taillabel=&quot;p1&quot; headlabel=&quot;p2&quot;]
    kpnmodem -- iptv1 [label=&quot;v2 (p#7)&quot; taillabel=&quot;p2&quot;]
    kpnmodem -- veraedge1 [label=&quot;v2&quot; taillabel=&quot;p4&quot;]

    ngs1 -- cn4 [label=&quot;v1,3&quot; taillabel=&quot;p10&quot; headlabel=&quot;p0&quot;]
    ngs1 -- ngs2 [label=&quot;v1,3 (p#4)&quot; taillabel=&quot;p1&quot; headlabel=&quot;p1&quot;]
    ngs1 -- ngs3 [label=&quot;v1,3&quot; taillabel=&quot;p8,9&quot; headlabel=&quot;p1,8&quot; penwidth=2.0]
    ngs1 -- cctv_sw [label=&quot;v3&quot; taillabel=&quot;p7&quot; headlabel=&quot;p5&quot;]

    ngs3 -- nd2 [label=&quot;v1,3&quot; taillabel=&quot;p7&quot;]
    ngs3 -- nd3 [label=&quot;v1,3&quot; taillabel=&quot;p8&quot;]

    cctv_sw -- ipcam1 [label=&quot;v3&quot; taillabel=&quot;p1&quot;]
    cctv_sw -- ipcam2 [label=&quot;v3 (p#1)&quot; taillabel=&quot;p2&quot;]
    cctv_sw -- ipcam3 [label=&quot;v3 (p#5)&quot; taillabel=&quot;p3&quot;]
  }
}

\```aafigure {&quot;foreground&quot;: &quot;#ff0000&quot;}
      +-----+   ^
      |     |   |
  ---&amp;gt;+     +---o---&amp;gt;
      |     |   |
      +-----+   V
```
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;Output:&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;dot {
graph NET {
layout=neato&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;edge [weight=2.0 fontsize=7]
node [style=filled shape=box]

node [fillcolor=white] kpnmodem
node [fillcolor=lightblue] ngs1 ngs2 ngs3
node [fillcolor=lightgreen] cctv_sw
node [fillcolor=silver] cn4 iptv1 veraedge1 nd2 nd3 philtv
node [fillcolor=yellow] wac1 wac2 wac3 owap1

kpnmodem -- cn4 [label=&quot;v2&quot; taillabel=&quot;p1&quot; headlabel=&quot;p2&quot;]
kpnmodem -- iptv1 [label=&quot;v2 (p#7)&quot; taillabel=&quot;p2&quot;]
kpnmodem -- veraedge1 [label=&quot;v2&quot; taillabel=&quot;p4&quot;]

ngs1 -- cn4 [label=&quot;v1,3&quot; taillabel=&quot;p10&quot; headlabel=&quot;p0&quot;]
ngs1 -- ngs2 [label=&quot;v1,3 (p#4)&quot; taillabel=&quot;p1&quot; headlabel=&quot;p1&quot;]
ngs1 -- ngs3 [label=&quot;v1,3&quot; taillabel=&quot;p8,9&quot; headlabel=&quot;p1,8&quot; penwidth=2.0]
ngs1 -- cctv_sw [label=&quot;v3&quot; taillabel=&quot;p7&quot; headlabel=&quot;p5&quot;]

ngs3 -- nd2 [label=&quot;v1,3&quot; taillabel=&quot;p7&quot;]
ngs3 -- nd3 [label=&quot;v1,3&quot; taillabel=&quot;p8&quot;]

cctv_sw -- ipcam1 [label=&quot;v3&quot; taillabel=&quot;p1&quot;]
cctv_sw -- ipcam2 [label=&quot;v3 (p#1)&quot; taillabel=&quot;p2&quot;]
cctv_sw -- ipcam3 [label=&quot;v3 (p#5)&quot; taillabel=&quot;p3&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}
}&lt;/p&gt;
&lt;div&gt;&lt;svg class=&quot;bob&quot; font-family=&quot;arial&quot; font-size=&quot;14&quot; height=&quot;80&quot; width=&quot;168&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;defs&gt;
&lt;marker id=&quot;triangle&quot; markerHeight=&quot;10&quot; markerUnits=&quot;strokeWidth&quot; markerWidth=&quot;10&quot; orient=&quot;auto&quot; refX=&quot;15&quot; refY=&quot;10&quot; viewBox=&quot;0 0 50 20&quot;&gt;
&lt;path d=&quot;M 0 0 L 30 10 L 0 20 z&quot;/&gt;
&lt;/marker&gt;
&lt;/defs&gt;
&lt;style&gt;

    line, path {
      stroke: black;
      stroke-width: 2;
      stroke-opacity: 1;
      fill-opacity: 1;
      stroke-linecap: round;
      stroke-linejoin: miter;
    }
    circle {
      stroke: black;
      stroke-width: 2;
      stroke-opacity: 1;
      fill-opacity: 1;
      stroke-linecap: round;
      stroke-linejoin: miter;
    }
    circle.solid {
      fill:black;
    }
    circle.open {
      fill:transparent;
    }
    tspan.head{
        fill: none;
        stroke: none;
    }
    
&lt;/style&gt;
&lt;path d=&quot; M 52 8 L 56 8 M 52 8 L 52 16 M 56 8 L 64 8 M 56 8 L 64 8 L 72 8 M 64 8 L 72 8 L 80 8 M 72 8 L 80 8 L 88 8 M 80 8 L 88 8 L 96 8 M 88 8 L 96 8 M 100 8 L 96 8 M 100 8 L 100 16 M 52 16 L 52 32 M 52 16 L 52 32 M 100 16 L 100 32 M 100 16 L 100 32 M 132 16 L 132 32 M 132 16 L 132 32 M 16 40 L 24 40 M 16 40 L 24 40 L 32 40 M 24 40 L 32 40 M 52 40 L 52 32 M 52 40 L 48 40 M 52 40 L 52 48 M 100 40 L 100 32 M 100 40 L 104 40 M 100 40 L 100 48 M 104 40 L 112 40 M 104 40 L 112 40 L 120 40 M 112 40 L 120 40 L 128 40 M 120 40 L 128 40 M 132 36 L 132 32 M 132 44 L 132 48 M 136 40 L 144 40 M 136 40 L 144 40 L 152 40 M 144 40 L 152 40 M 52 48 L 52 64 M 52 48 L 52 64 M 100 48 L 100 64 M 100 48 L 100 64 M 52 72 L 52 64 M 52 72 L 56 72 L 64 72 M 56 72 L 64 72 L 72 72 M 64 72 L 72 72 L 80 72 M 72 72 L 80 72 L 88 72 M 80 72 L 88 72 L 96 72 M 88 72 L 96 72 M 100 72 L 100 64 M 100 72 L 96 72&quot; fill=&quot;none&quot;/&gt;
&lt;path d=&quot;&quot; fill=&quot;none&quot; stroke-dasharray=&quot;3 3&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;132&quot; x2=&quot;132&quot; y1=&quot;16&quot; y2=&quot;4&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;32&quot; x2=&quot;44&quot; y1=&quot;40&quot; y2=&quot;40&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;32&quot; x2=&quot;44&quot; y1=&quot;40&quot; y2=&quot;40&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;40&quot; x2=&quot;44&quot; y1=&quot;40&quot; y2=&quot;40&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;152&quot; x2=&quot;164&quot; y1=&quot;40&quot; y2=&quot;40&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;152&quot; x2=&quot;164&quot; y1=&quot;40&quot; y2=&quot;40&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;160&quot; x2=&quot;164&quot; y1=&quot;40&quot; y2=&quot;40&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;132&quot; x2=&quot;132&quot; y1=&quot;48&quot; y2=&quot;76&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;132&quot; x2=&quot;132&quot; y1=&quot;48&quot; y2=&quot;76&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;132&quot; x2=&quot;132&quot; y1=&quot;64&quot; y2=&quot;76&quot;/&gt;
&lt;circle class=&quot;open&quot; cx=&quot;132&quot; cy=&quot;40&quot; r=&quot;4&quot;/&gt;
&lt;circle class=&quot;open&quot; cx=&quot;132&quot; cy=&quot;40&quot; r=&quot;4&quot;/&gt;
&lt;circle class=&quot;open&quot; cx=&quot;132&quot; cy=&quot;40&quot; r=&quot;4&quot;/&gt;
&lt;circle class=&quot;open&quot; cx=&quot;132&quot; cy=&quot;40&quot; r=&quot;4&quot;/&gt;
&lt;/svg&gt;
&lt;/div&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;Others&quot; name=&quot;Others&quot;&gt;Others&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;#++&lt;/code&gt; and &lt;code&gt;#--&lt;/code&gt; for headown&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$include: file.md $&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Nanowiki</title>
<link href="https://www.0ink.net/posts/2022/2022-10-10-nanowiki.html"></link>
<id>urn:uuid:25c4d0c7-223f-d749-d1a9-ac58f0833184</id>
<updated>2022-10-12T00:00:00+02:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[NanoWiki is a Wiki implementation based on picowiki.
I have been using SimpleNote for a number of years.  It works pretty well
but somehow I was looking for:

Ability to include and render nice asciiart pictures
Organizes articles in a folder structure.
...]]></summary>
<content type="html">&lt;p&gt;&lt;a href=&quot;https://github.com/iliu-net/nanowiki&quot;&gt;NanoWiki&lt;/a&gt; is a Wiki implementation based on &lt;a href=&quot;https://github.com/luckyshot/picowiki&quot;&gt;picowiki&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I have been using &lt;a href=&quot;https://simplenote.com/&quot;&gt;SimpleNote&lt;/a&gt; for a number of years.  It works pretty well
but somehow I was looking for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ability to include and render nice asciiart pictures&lt;/li&gt;
&lt;li&gt;Organizes articles in a folder structure.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So I was looking for a Wiki package that could either do this or be extended
to do this.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/iliu-net/nanowiki/raw/main/static/screenshot.png&quot; alt=&quot;screenshot&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Other features that I was looking for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use of &lt;a href=&quot;https://daringfireball.net/projects/markdown/&quot;&gt;markdown&lt;/a&gt; for markup, and be able to tweak the format as needed.&lt;/li&gt;
&lt;li&gt;Editor that would &lt;em&gt;syntax highlight&lt;/em&gt; the markdown syntax&lt;/li&gt;
&lt;li&gt;Store data as simple files&lt;/li&gt;
&lt;li&gt;Written in a language I am familiar with.&lt;/li&gt;
&lt;li&gt;software generated network graphs (graphviz)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, after looking at a number of packages, I opted for one that allthough
did not have all features, but it was small enough and easily extendable.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/luckyshot/picowiki&quot;&gt;PicoWiki&lt;/a&gt; is a very small Wiki implementation with a &lt;em&gt;plugin&lt;/em&gt;
architecture, so it is quite easy to extend.  The downside of this
is that the functionality in &lt;a href=&quot;https://github.com/luckyshot/picowiki&quot;&gt;PicoWiki&lt;/a&gt; is quite limited.  So
I added the following features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;file management: create, delete, rename, modify, attach, etc.&lt;/li&gt;
&lt;li&gt;hooks for access control&lt;/li&gt;
&lt;li&gt;meta data support&lt;/li&gt;
&lt;li&gt;Disabled code execution.  This can be considered a &lt;em&gt;&amp;quot;security&amp;quot;&lt;/em&gt; feature.&lt;/li&gt;
&lt;li&gt;Support for byte ranges.  This lets you stream video files directly
from the wiki.&lt;/li&gt;
&lt;li&gt;toggable, folder or document views.&lt;/li&gt;
&lt;li&gt;theme support&lt;/li&gt;
&lt;li&gt;Multiple file type handling&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The default installations has the following plugins:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Emoji : Render emojis&lt;/li&gt;
&lt;li&gt;HTML : HTML content handler&lt;/li&gt;
&lt;li&gt;MarkDown : Markdown content handler&lt;/li&gt;
&lt;li&gt;Includes : Include Wiki documents in another&lt;/li&gt;
&lt;li&gt;Vars : Expand variables.  Either from document metadata or from the NanoWiki config file.&lt;/li&gt;
&lt;li&gt;WikiLinks : short hand for wiki links.&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>X728 kit for Raspberry Pi 4</title>
<link href="https://www.0ink.net/posts/2022/2022-10-01-x728.html"></link>
<id>urn:uuid:90557427-7030-f41c-50ed-ec0b015336c5</id>
<updated>2024-02-02T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[As part of my small project of movng my Z-Wave Hub to a Raspberry PI, I got an
X728 kit.  This has:

UPS controller board

RTC circuit
...]]></summary>
<content type="html">&lt;p&gt;As part of my small project of movng my Z-Wave Hub to a Raspberry PI, I got an
&lt;a href=&quot;http://wiki.geekworm.com/X728&quot;&gt;X728&lt;/a&gt; kit.  This has:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;UPS controller board
&lt;ul&gt;
&lt;li&gt;RTC circuit&lt;/li&gt;
&lt;li&gt;Battery and Power control board&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Case
&lt;ul&gt;
&lt;li&gt;Button&lt;/li&gt;
&lt;li&gt;Cooling fan&lt;/li&gt;
&lt;li&gt;Additional Battery holder&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The case has holes for wall-mounting.&lt;/p&gt;
&lt;p&gt;The Geekworm X728 kit is very easy to build.  There is a video to show how to do
this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=QOG30LXb6ds&amp;amp;t&quot;&gt;Build video&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Otherwise, refer to the hardware guide &lt;a href=&quot;http://wiki.geekworm.com/X728-hardware&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In my case, before the build, I took the disassembled case to measure the holes
needed for wall-mounting the case. You need fairly small screws for this. I
actually had to bend the case slighly for my screws to work.&lt;/p&gt;
&lt;p&gt;Also, I set the jumper to automatic Power-on and a few cable ties to fix a USB Hub
to the case.&lt;/p&gt;
&lt;p&gt;To test the hardware I downloaded a 64-bit Raspberry OS Lite image from
&lt;a href=&quot;https://www.raspberrypi.com/software/operating-systems/&quot;&gt;Raspberrypi.com&lt;/a&gt; and
image an micro-SD card.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Boot the Raspberry OS.  The first boot will resize the filesystems, so please
wait.  Also, it will let you configure the default user and password.&lt;/li&gt;
&lt;li&gt;Enable the i2c function:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sudo raspi-config&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Go to &lt;code&gt;Interfacing Options&lt;/code&gt; -&amp;gt; &lt;code&gt;I2C - Enable/Disable automatic loading&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;While you are at-it, you may also enable &lt;code&gt;SSH&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Alternatively, you can a manual install by:
&lt;ul&gt;
&lt;li&gt;Modify the &lt;code&gt;config.txt&lt;/code&gt; in the &lt;code&gt;/boot&lt;/code&gt; partition:&lt;/li&gt;
&lt;li&gt;Add at the end:&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[all]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dtparam=i2c_arm=on&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Install pre-requisites:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sudo apt-get update&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sudo apt-get upgrade&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sudo apt-get -y install i2c-tools&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;This is only needed for &lt;code&gt;i2cdetect&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Reboot the system.&lt;/li&gt;
&lt;li&gt;Check if the hardware is detected:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sudo i2cdetect -y 1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;img src=&quot;https://raw.githubusercontent.com/alejandroliu/0ink.net/main/snippets/2022/X728/imgs/X728x-i2c.png&quot; alt=&quot;screenshot&quot; /&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;#36&lt;/code&gt; - the address of the battery fuel gauging chip&lt;/li&gt;
&lt;li&gt;&lt;code&gt;#68&lt;/code&gt; - the address of the RTC chip&lt;/li&gt;
&lt;li&gt;Different x728 versions may have different values.  Mine used these values.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I personally did not like the example software.  This can be found in &lt;a href=&quot;https://github.com/geekworm-com/x728&quot;&gt;github&lt;/a&gt;.
Specifically, the &lt;code&gt;shutdown&lt;/code&gt; functionality seemed to have &lt;a href=&quot;https://en.wikipedia.org/wiki/Race_condition&quot;&gt;race-conditions&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;However, using it is not that complicated.  So I wrote my own software, but you
can do your own thing:&lt;/p&gt;
&lt;h2 id=&quot;RTC+functionality&quot; name=&quot;RTC+functionality&quot;&gt;RTC functionality&lt;/h2&gt;
&lt;p&gt;The RTC functionality is supported by the Raspberry OS kernel.  You need to enable
the &lt;code&gt;i2c&lt;/code&gt; functionality in &lt;code&gt;/boot/config.txt&lt;/code&gt; by adding the line:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-config&quot;&gt;dtparam=i2c_arm=on&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With that enabled, you need to add the kernel modules:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;i2c-dev
rtc-ds1307&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You need to enable it in the bus:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;echo ds1307 0x68 &amp;gt; /sys/class/i2c-adapter/i2c-1/new_device&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From then on, you can use the standard &lt;code&gt;hwclock&lt;/code&gt; command.&lt;/p&gt;
&lt;p&gt;These can be added to &lt;code&gt;rc.local&lt;/code&gt; which is how the sample code does.  I prefer to
do this from a script run from &lt;code&gt;systemd&lt;/code&gt; unit file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-conf&quot;&gt;# file: /etc/systemd/system/x728clock.service

[Unit]
Description=Restore / save X728 clock
DefaultDependencies=no
Before=sysinit.target shutdown.target
Conflicts=shutdown.target

[Service]
ExecStart=/etc/x728/clock.sh start
ExecStop=/etc/x728/clock.sh stop
Type=oneshot
RemainAfterExit=yes

[Install]
WantedBy=sysinit.target
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After saving this file and creating a &lt;code&gt;/etc/x728/clock.sh&lt;/code&gt; script you can:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl daemon-reload
systemctl enable x728clock
systemctl start x728clock
systemctl disable fake-hwclock
systemctl stop fake-hwclock&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/snippets/2022/X728/x728clock.service&quot;&gt;systemd unit file&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/snippets/2022/X728/scripts/clock.sh&quot;&gt;script&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;GPIO+assignments&quot; name=&quot;GPIO+assignments&quot;&gt;GPIO assignments&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pin&lt;/th&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;th&gt;Direction&lt;/th&gt;
&lt;th&gt;Comment&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;#6&lt;/td&gt;
&lt;td&gt;PLD&lt;/td&gt;
&lt;td&gt;in&lt;/td&gt;
&lt;td&gt;1: A/C lost, 0: A/C OK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#5&lt;/td&gt;
&lt;td&gt;5hutdown&lt;/td&gt;
&lt;td&gt;in&lt;/td&gt;
&lt;td&gt;Sense button press&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#12&lt;/td&gt;
&lt;td&gt;Boot&lt;/td&gt;
&lt;td&gt;out&lt;/td&gt;
&lt;td&gt;Control SW/HW controlled button&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#20&lt;/td&gt;
&lt;td&gt;Buzzer&lt;/td&gt;
&lt;td&gt;out&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#26&lt;/td&gt;
&lt;td&gt;Button&lt;/td&gt;
&lt;td&gt;out&lt;/td&gt;
&lt;td&gt;Simulate power button press&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;Reading &lt;code&gt;PLD&lt;/code&gt; detects if the A/C power is available or not.  If it reads &lt;code&gt;1&lt;/code&gt;
A/C power was lost.  &lt;code&gt;0&lt;/code&gt; if A/C power is available.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Buzzer&lt;/code&gt; if set to &lt;code&gt;1&lt;/code&gt; it will sound a rather loud beep.  &lt;code&gt;0&lt;/code&gt; for off.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Button&lt;/code&gt; simulates pressing the hardware &lt;code&gt;Off&lt;/code&gt; button.  If you set &lt;code&gt;Button&lt;/code&gt;
to &lt;code&gt;1&lt;/code&gt; for 6 seconds, the system will poweroff.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Shutdown&lt;/code&gt; is used to read the status of the hardware button.  This works only
if &lt;code&gt;Boot&lt;/code&gt; is set to &lt;code&gt;1&lt;/code&gt;.  Otherwise, &lt;code&gt;Shutdown&lt;/code&gt; doesn&#039;t seem to work.
When &lt;code&gt;Boot&lt;/code&gt; is set to &lt;code&gt;1&lt;/code&gt;, it would read &lt;code&gt;1&lt;/code&gt; if pressed, &lt;code&gt;0&lt;/code&gt; if released.
Weirdly enough, the &lt;code&gt;Shutdown&lt;/code&gt; button is not very sensitive.  It takes about
3 seconds to register the button press.  The button release takes bout 50
seconds to detect.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;GPIO+programming&quot; name=&quot;GPIO+programming&quot;&gt;GPIO programming&lt;/h2&gt;
&lt;p&gt;Programming GPIO is quite easy.  It can be done from shell scripting using the
sys file-system.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpioIO() {
  local pin=$1
  if [ $# -eq 1 ] ; then
    cat /sys/class/gpio/gpio$pin/value
  else
    echo &quot;$2&quot; &amp;gt; /sys/class/gpio/gpio$pin/value
  fi
}

gpioInit() {
  local name=&quot;$1&quot; pin=&quot;$2&quot; dir=&quot;$3&quot;

  [ ! -d /sys/class/gpio/gpio$pin ] &amp;amp;&amp;amp; echo &quot;$pin&quot; &amp;gt; /sys/class/gpio/export
  echo $dir &amp;gt; /sys/class/gpio/gpio$pin/direction

  eval &quot;gpio${name}() { gpioIO $pin \&quot;\$@\&quot; ; }&quot;
}
ticks() {
  echo $(date +%s)$(date +%N | cut -c-2)
}

beep() {
  local len=&quot;$1&quot; ; shift

  gpioBUZZER 1
  sleep &quot;$len&quot;
  gpioBUZZER 0

  [ $# -eq 0 ] &amp;amp;&amp;amp; return

  local repeat=&quot;$1&quot; idle
  [ $# -gt 1 ] &amp;amp;&amp;amp; idle=&quot;$2&quot; || idle=&quot;$len&quot;

  while [ $repeat -gt 1 ]
  do
    repeat=$(expr $repeat - 1)
    sleep &quot;$idle&quot;
    gpioBUZZER 1
    sleep &quot;$len&quot;
    gpioBUZZER 0
  done
}

gpioInit SHUTDOWN 5 in
gpioInit PLD 6 in
gpioInit BOOT 12 out
gpioInit BUZZER 20 out
gpioInit BUTTON 26 out
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Afterwards, you can just:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;gpio[PIN]&lt;/code&gt; to read, i.e:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;gpioSHUTDOWN&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gpioPLD&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gpio[PIN] {1|0}&lt;/code&gt; to write i.e.:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;gpioBOOT 1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gpioBUZER 0&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Reading+Battery+status&quot; name=&quot;Reading+Battery+status&quot;&gt;Reading Battery status&lt;/h2&gt;
&lt;p&gt;You can read battery voltage and battery charge from the &lt;code&gt;smbus&lt;/code&gt;.  To read the &lt;code&gt;smbus&lt;/code&gt;
I am using an example from [rpi-examples]((&lt;a href=&quot;https://github.com/leon-anavi/rpi-examples/tree/master/BMP180/c&quot;&gt;https://github.com/leon-anavi/rpi-examples/tree/master/BMP180/c&lt;/a&gt;).
Specifically, I am only using the files &lt;code&gt;smbus.c&lt;/code&gt; and &lt;code&gt;smbus.h&lt;/code&gt; from that repository.&lt;/p&gt;
&lt;p&gt;The code outline is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;open &lt;code&gt;/dev/i2c-1&lt;/code&gt; in read/write mode.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ioctl(fd, I2C_SLAVE, I2C_ADDRESS)&lt;/code&gt; where &lt;code&gt;I2C_ADDRESS = 0x36&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;From &lt;code&gt;smbus.c&lt;/code&gt; read &lt;code&gt;i2c_smbus_read_word_data(fd, address)&lt;/code&gt;, and byte swap.&lt;/li&gt;
&lt;li&gt;Voltage can be read from address &lt;code&gt;2&lt;/code&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Voltage = (swapped) * 1.25 / 1000 / 16&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Battery charge can be read from address &lt;code&gt;4&lt;/code&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Battery = (swapped) / 256&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The code to do this can be found on &lt;a href=&quot;https://github.com/alejandroliu/0ink.net/tree/main/snippets/2022/X728/src&quot;&gt;github&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A precompiled 64bit static binary can be found there too.&lt;/p&gt;
&lt;h2 id=&quot;Power+down&quot; name=&quot;Power+down&quot;&gt;Power down&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;http://wiki.geekworm.com/X728&quot;&gt;x728&lt;/a&gt; will turn off power by holding down the power button for around
6 seconds.  Doing this will skip the &lt;code&gt;shutdown&lt;/code&gt; process.  Also, if you execute the
&lt;code&gt;poweroff&lt;/code&gt; command, the Raspberry Pi will shutdown but power will not go &lt;strong&gt;OFF&lt;/strong&gt; until
you hold the power button for 6 seconds.&lt;/p&gt;
&lt;p&gt;For this to work properly, I am adding this small script to &lt;code&gt;/lib/systemd/system-shutdown/gpio-poweroff&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/sh
#
# file: /lib/systemd/system-shutdown/gpio-poweroff
# $1 will be either &quot;halt&quot;, &quot;poweroff&quot;, &quot;reboot&quot; or &quot;kexec&quot;
#

BUTTON=26

op_poweroff() {
  echo $BUTTON &amp;gt; /sys/class/gpio/export
  echo out &amp;gt; /sys/class/gpio/gpio$BUTTON/direction
  echo 1 &amp;gt; /sys/class/gpio/gpio$BUTTON/value
  sync;sync;sync
  sleep 7
  echo 0 &amp;gt; /sys/class/gpio/gpio$BUTTON/value
  sleep 3
}

case &quot;$1&quot; in
  poweroff) op_poweroff ;;
esac
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This hooks into &lt;code&gt;systemd&lt;/code&gt;&#039;s &lt;code&gt;shutdown&lt;/code&gt; target and uses the &lt;code&gt;BUTTON&lt;/code&gt; pin to
simulate holding the button for 6 seconds to force the UPS board to power off.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/snippets/2022/X728/gpio-poweroff&quot;&gt;script&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;UPS+management&quot; name=&quot;UPS+management&quot;&gt;UPS management&lt;/h2&gt;
&lt;p&gt;In addition, I wrote a small script to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;graceful shutdown when power button is pressed.
&lt;ul&gt;
&lt;li&gt;hold the power button, after approximately 3 seconds, you will hear
2 beeps.  YOu can release the power button then.  The system will
do a graceful shutdown and power down.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;When A/C power is lost:
&lt;ul&gt;
&lt;li&gt;If battery status can not be determined, the system will do a graceful
powerdown.&lt;/li&gt;
&lt;li&gt;If battery status can be read, it will beep once every 60 seconds until
power is restored.&lt;/li&gt;
&lt;li&gt;If battery is low, it will do a graceful powerdown.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Events are written to /dev/kmsg, so they could be forwarded to a syslog server.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The files to do this are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/snippets/2022/X728/x728ups.service&quot;&gt;systemd unit file&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/snippets/2022/X728/scripts/upsmon.sh&quot;&gt;upsmon.sh&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Home+Assistant&quot; name=&quot;Home+Assistant&quot;&gt;Home Assistant&lt;/h2&gt;
&lt;p&gt;I am using the &lt;a href=&quot;http://wiki.geekworm.com/X728&quot;&gt;X728 kit&lt;/a&gt; for creating a &lt;a href=&quot;https://www.home-assistant.io/&quot;&gt;Home Assistant&lt;/a&gt; installation.
&lt;a href=&quot;https://www.home-assistant.io/&quot;&gt;Home Assistant&lt;/a&gt; has a &amp;quot;managed Operating System&amp;quot; called
&lt;a href=&quot;https://github.com/home-assistant/operating-system&quot;&gt;Home Assistant OS&lt;/a&gt; which is a mostly read-only installation.  This
makes it complicated to add your own &amp;quot;low-level&amp;quot; customizations.  To include
these scripts and making them persistant accross upgrades I am hooking into the
&lt;a href=&quot;https://rauc.io/&quot;&gt;RAUC OTA&lt;/a&gt; upgrade subsystem.&lt;/p&gt;
&lt;p&gt;For that, I hook up to the &lt;a href=&quot;https://rauc.readthedocs.io/en/latest/using.html#system-based-customization-handlers&quot;&gt;System handlers&lt;/a&gt;
which makes use of a &lt;a href=&quot;https://rauc.readthedocs.io/en/latest/reference.html#sec-handler-interface&quot;&gt;Handler Interface&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With these scripts, I am able to move the customizations from a previous image
to the new upgraded image.&lt;/p&gt;
&lt;p&gt;For this, I have a script &lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/snippets/2022/X728/OTA/haos-x728.sh&quot;&gt;haos-x728&lt;/a&gt;
that injects the customizations
into a new installation image.  This script also modifies &lt;code&gt;/etc/rauc/system.conf&lt;/code&gt;
so that the customization handler is called during an upgrade.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/snippets/2022/X728/OTA/post-install&quot;&gt;post-install handler&lt;/a&gt;
re-adds the handler to &lt;code&gt;/etc/rauc/system.conf&lt;/code&gt;
and copies the necessary files to the updated image.&lt;/p&gt;
&lt;p&gt;The customization scripts are &lt;em&gt;not&lt;/em&gt; &lt;a href=&quot;http://wiki.geekworm.com/X728&quot;&gt;X728&lt;/a&gt; specific, and essentially lets
you copy all the files in a directory to the custom image.  As such, I am using
to not only inject these &lt;a href=&quot;http://wiki.geekworm.com/X728&quot;&gt;X728&lt;/a&gt; scripts, but also the &lt;code&gt;vcgencmd&lt;/code&gt; and a
&lt;code&gt;muninlite&lt;/code&gt; agent.  Also, the dependant binaries for the
&lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/snippets/2022/X728/OTA/post-install&quot;&gt;post-install handler&lt;/a&gt;
are injected in the same way.&lt;/p&gt;
&lt;p&gt;For the &lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/snippets/2022/X728/OTA/post-install&quot;&gt;post-install handler&lt;/a&gt;
to work properly you need to copy binaries for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;gensquashfs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sqfs2tar&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And dependant shared libraries (that are not part of the &lt;a href=&quot;https://github.com/home-assistant/operating-system&quot;&gt;Home Assistant OS&lt;/a&gt;
image:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;liblz4.so.1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;liblz4.so.1.9.3&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;liblzma.so.5&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;liblzma.so.5.2.5&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;liblzo2.so.2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;liblzo2.so.2.0.0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;libselinux.so.1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;libsquashfs.so.1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;libsquashfs.so.1.1.0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;libzstd.so.1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;libzstd.so.1.4.8&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The simplest way to get these is to install &lt;code&gt;squashfs-tools-ng&lt;/code&gt; on standard
&lt;code&gt;Raspberry PI OS&lt;/code&gt; and copy those files from there.&lt;/p&gt;
&lt;p&gt;The full set of customization files that I am using can be found
&lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/snippets/2022/X728/OTA/x728rootfs.tar.gz&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It contains:&lt;/p&gt;
&lt;p&gt;RAUC handler:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;lib/rauc/post-install&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;squashfs-tools-ng&lt;/code&gt; (dependancy to RAUC handler)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;bin/gensquashfs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bin/sqfs2tar&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lib/liblz4.so.1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lib/liblz4.so.1.9.3&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lib/liblzma.so.5&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lib/liblzma.so.5.2.5&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lib/liblzo2.so.2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lib/liblzo2.so.2.0.0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lib/libselinux.so.1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lib/libsquashfs.so.1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lib/libsquashfs.so.1.1.0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lib/libzstd.so.1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lib/libzstd.so.1.4.8&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Actual &lt;a href=&quot;http://wiki.geekworm.com/X728&quot;&gt;X728&lt;/a&gt; support scripts.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;bin/x728batt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;etc/x728/clock.sh&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;etc/x728/upsmon.sh&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;etc/systemd/system/x728clock.service&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;etc/systemd/system/x728ups.service&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;etc/systemd/system/sysinit.target.wants/x728clock.service&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;etc/systemd/system/multi-user.target.wants/x728ups.service&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lib/systemd/system-shutdown/gpio-poweroff&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Munin node:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;bin/munin-node&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;etc/systemd/system/sockets.target.wants/munin-node.socket&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;etc/systemd/system/munin-node.socket&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;etc/systemd/system/munin-node@.service&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;etc/muninlite.conf&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>cuylib</title>
<link href="https://www.0ink.net/posts/2022/2022-09-20-cuylib.html"></link>
<id>urn:uuid:be57f2ce-6906-558d-7480-de0e71817638</id>
<updated>2024-02-02T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This is a tiny library to implement Web server embedded editor.
You can find it in github.
Can be used either from haserl or directly from
a shell script.
Features:

...]]></summary>
<content type="html">&lt;p&gt;This is a tiny library to implement Web server embedded editor.&lt;/p&gt;
&lt;p&gt;You can find it in &lt;a href=&quot;https://github.com/alejandroliu/0ink.net/tree/main/snippets/2022/cuylib&quot;&gt;github&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Can be used either from &lt;a href=&quot;http://haserl.sourceforge.net/&quot;&gt;haserl&lt;/a&gt; or directly from
a &lt;code&gt;shell&lt;/code&gt; script.&lt;/p&gt;
&lt;p&gt;Features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Uses &lt;a href=&quot;https://codemirror.net/&quot;&gt;codemirror&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Escaped HTML entities (html_enc)&lt;/li&gt;
&lt;li&gt;Decode URL escaping (url_decode)&lt;/li&gt;
&lt;li&gt;Read POST form data (post_data)&lt;/li&gt;
&lt;li&gt;Parse &lt;code&gt;QUERY_STRING&lt;/code&gt; (query_string and query_string_raw)&lt;/li&gt;
&lt;li&gt;Render HTML and Markdown documents with pre-processing (cuy_render)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Support functions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;codemirror_link : Configured URL to where you can find codemirror files.&lt;/li&gt;
&lt;li&gt;html_msg : generate a HTML response&lt;/li&gt;
&lt;li&gt;html_enc : Encode special HTML characters&lt;/li&gt;
&lt;li&gt;url_decode : Decode URL encoded strings&lt;/li&gt;
&lt;li&gt;post_data : Read data posted using a HTML POST request.&lt;/li&gt;
&lt;li&gt;query_string_raw : parse HTML query parameters.&lt;/li&gt;
&lt;li&gt;query_string : part HTML query paramters and also decodes URL encoding.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Editor components:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;cuy_header : snippet of code for the html document header.&lt;/li&gt;
&lt;li&gt;cuy_editform : snippet of code for genereting the html editor form&lt;/li&gt;
&lt;li&gt;cuy_editarea : snippet of code to bind codemirror to a text area&lt;/li&gt;
&lt;li&gt;cuy_savecb : snippet for save command callback&lt;/li&gt;
&lt;li&gt;cuy_render : convert content into suitable HTML markup&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Main editing entry point&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;cuy_editapp : A full editing page&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Photoprism</title>
<link href="https://www.0ink.net/posts/2022/2022-09-10-photoprism.html"></link>
<id>urn:uuid:3b1a2d85-263e-a102-7a86-024a5fc8fb47</id>
<updated>2022-09-20T00:00:00+02:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[photoprism is a web based photo management application.
From its website:

PhotoPrism® is an AI-Powered Photos App for the Decentralized Web.
It makes use of the latest technologies to tag and find pictures
automatically without getting in your way. You can run it at home,
...]]></summary>
<content type="html">&lt;p&gt;&lt;a href=&quot;https://photoprism.app/&quot;&gt;photoprism&lt;/a&gt; is a web based photo management application.&lt;/p&gt;
&lt;p&gt;From its website:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;PhotoPrism® is an AI-Powered Photos App for the Decentralized Web.
It makes use of the latest technologies to tag and find pictures
automatically without getting in your way. You can run it at home,
on a private server, or in the cloud.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://docs.photoprism.app/img/preview.jpg&quot; alt=&quot;photoprism preview&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Browse all your photos and videos without worrying about RAW conversion,
duplicates or video formats&lt;/li&gt;
&lt;li&gt;search: Easily find specific pictures using powerful search filters&lt;/li&gt;
&lt;li&gt;places: Includes four high-resolution world maps to bring back the
memories of your favorite trips&lt;/li&gt;
&lt;li&gt;Play Live Photos™ by hovering over them in albums and search results&lt;/li&gt;
&lt;li&gt;people: Recognizes the faces of your family and friends&lt;/li&gt;
&lt;li&gt;Automatic classification of pictures based on their content and location&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What I found is that it does most things automatically.&lt;/p&gt;
&lt;p&gt;My implementation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I use a docker instance for &lt;a href=&quot;https://photoprism.app/&quot;&gt;photoprism&lt;/a&gt; linked to a &lt;code&gt;mysql&lt;/code&gt; database.
(actually &lt;code&gt;mariadb&lt;/code&gt;).
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.photoprism.app/getting-started/advanced/databases/&quot;&gt;database setup&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;This &lt;a href=&quot;https://photoprism.app/&quot;&gt;photoprism&lt;/a&gt; instance is set as &lt;code&gt;AUTH=public&lt;/code&gt;, so no authentication.
Users on my home network can connect directly.  From the Internet, I am using an
&lt;a href=&quot;https://nginx.org/&quot;&gt;nginx&lt;/a&gt; reverse proxy.  This reverse proxy requires authentication.&lt;/li&gt;
&lt;li&gt;For photo sharing, a different &lt;a href=&quot;https://photoprism.app/&quot;&gt;photoprism&lt;/a&gt; instance is used pointing to the same
file system and mysql as the main instance.  This instance however has &lt;code&gt;AUTH=password&lt;/code&gt;
enabled, so only &lt;code&gt;shared&lt;/code&gt; links can be visited.&lt;/li&gt;
&lt;li&gt;For uploading photos from my iPhone, I am using &lt;a href=&quot;https://link.photoprism.app/photosync&quot;&gt;PhotoSync&lt;/a&gt;.  This can be
configured to upload directly to &lt;a href=&quot;https://photoprism.app/&quot;&gt;photoprism&lt;/a&gt; using WebDav.  (See instructions for
&lt;a href=&quot;https://docs.photoprism.app/user-guide/sync/mobile-devices/&quot;&gt;syncing with mobile devices&lt;/a&gt;.)
On the other hand, it is complicated for me due to permission problems in my home
network.  For that reason, I am using a small container running a &lt;code&gt;sshd&lt;/code&gt; daemon
and I sync to that small container using &lt;code&gt;sftp&lt;/code&gt; protocol.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some issues I found:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;face recognition is a bit wonky.  Specially for kid&#039;s faces.&lt;/li&gt;
&lt;li&gt;some tweaks may be needed to get things to display just right.&lt;/li&gt;
&lt;li&gt;running using the embedded &lt;code&gt;sqlite&lt;/code&gt; database won&#039;t scale beyond a handful of
photos.&lt;/li&gt;
&lt;li&gt;I have a photo library of 400GB.  It took a long time to index.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Before I would copy files from my camera to my server.  In the case of
video files, a re-encoding step was needed.  With todays smartphones
this is not needed.  The smartphone can sync directly to the server
and files are already compressed with a good enough CODEC.&lt;/p&gt;</content>
</entry>
<entry>
<title>SupervisorUI MF</title>
<link href="https://www.0ink.net/posts/2022/2022-09-01-supervisorui-mf.html"></link>
<id>urn:uuid:40ae0602-1d4b-729b-9290-5e692e12865c</id>
<updated>2022-09-01T00:00:00+02:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[In a previous article, I updated a supervisorui project to work for me.
This updated version supervisorui-redone is essentially a PHP application
which is a different approach from the original supervisorui project
which was more of a JavaScript application with some helper functionality implemented
in PHP.
As such, I figured that I could probably fix the supervisorui code base
...]]></summary>
<content type="html">&lt;p&gt;In a previous &lt;a href=&quot;/posts/2022/2022-08-25-supervisorui-redone.html&quot;&gt;article&lt;/a&gt;, I updated a &lt;a href=&quot;https://github.com/Tabcorp/supervisorui/&quot;&gt;supervisorui&lt;/a&gt; project to work for me.&lt;/p&gt;
&lt;p&gt;This updated version &lt;a href=&quot;https://github.com/TortugaLabs/supervisorui-redone&quot;&gt;supervisorui-redone&lt;/a&gt; is essentially a PHP application
which is a different approach from the original &lt;a href=&quot;https://github.com/Tabcorp/supervisorui/&quot;&gt;supervisorui&lt;/a&gt; project
which was more of a JavaScript application with some helper functionality implemented
in PHP.&lt;/p&gt;
&lt;p&gt;As such, I figured that I could probably fix the &lt;a href=&quot;https://github.com/Tabcorp/supervisorui/&quot;&gt;supervisorui&lt;/a&gt; code base
eliminating the &lt;code&gt;Silex&lt;/code&gt; library, but keeping most of the JavaScript framework
in place.  This resulted in my &lt;a href=&quot;https://github.com/TortugaLabs/SupervisorUI-mf&quot;&gt;SupervisorUI-mf&lt;/a&gt; dashboard.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/TortugaLabs/SupervisorUI-mf/master/screenshot.png&quot; alt=&quot;screenshot&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This maintains JavaScript framework and simply removes the &lt;code&gt;Silex&lt;/code&gt; dependancy.&lt;/p&gt;
&lt;p&gt;As before, the &lt;a href=&quot;http://scripts.incutio.com/xmlrpc/&quot;&gt;Incutio XML-RPC Library&lt;/a&gt; was
updated so that it works with PHP8.&lt;/p&gt;
&lt;p&gt;In addition I added/fixed the following functionality:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Added links to &lt;a href=&quot;http://supervisord.org/index.html&quot;&gt;supervisor&lt;/a&gt; built-in web UI.&lt;/li&gt;
&lt;li&gt;Added links to &lt;code&gt;restart&lt;/code&gt; and &lt;code&gt;reload config&lt;/code&gt; &lt;a href=&quot;http://supervisord.org/index.html&quot;&gt;supervisor&lt;/a&gt; daemons.&lt;/li&gt;
&lt;li&gt;Fixed the &lt;code&gt;updateServers&lt;/code&gt; functionality, so that services status gets updated
every 30 seconds without having to reload the page.  Similarly, starting/stopping
services do not need to reload the page as with &lt;a href=&quot;https://github.com/TortugaLabs/supervisorui-redone&quot;&gt;supervisorui-redone&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Added the ability to configure port&#039;s in the server IP specifications.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So the result is an updated &lt;a href=&quot;https://github.com/Tabcorp/supervisorui/&quot;&gt;supervisorui&lt;/a&gt; which works at least for me.&lt;/p&gt;
&lt;p&gt;Note that I am still keeping &lt;a href=&quot;https://github.com/TortugaLabs/supervisorui-redone&quot;&gt;supervisorui-redone&lt;/a&gt; because it is more
static than &lt;a href=&quot;https://github.com/TortugaLabs/SupervisorUI-mf&quot;&gt;SupervisorUI-mf&lt;/a&gt; which means that &lt;a href=&quot;https://github.com/TortugaLabs/supervisorui-redone&quot;&gt;supervisorui-redone&lt;/a&gt;
works better with &lt;strong&gt;High Latency&lt;/strong&gt; (high ping time) connections.&lt;/p&gt;</content>
</entry>
<entry>
<title>Supervisorui REDONE</title>
<link href="https://www.0ink.net/posts/2022/2022-08-25-supervisorui-redone.html"></link>
<id>urn:uuid:b4115a52-6ed6-5e4b-2b4d-0edcaac97fd1</id>
<updated>2022-08-25T00:00:00+02:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Currently I am using docker containers to deploy applications.  A number of those
containers make use of supervisord for managing processes.  While
supervisord itself comes with a UI, it is unhandy for me because each
container is its own supervisord instance.
So I was interested in some software that would let me manage multiple
supervisord instances in a single page.  Turns out that there
...]]></summary>
<content type="html">&lt;p&gt;Currently I am using docker containers to deploy applications.  A number of those
containers make use of &lt;a href=&quot;http://supervisord.org/index.html&quot;&gt;supervisord&lt;/a&gt; for managing processes.  While
&lt;a href=&quot;http://supervisord.org/index.html&quot;&gt;supervisord&lt;/a&gt; itself comes with a UI, it is unhandy for me because each
container is its own &lt;a href=&quot;http://supervisord.org/index.html&quot;&gt;supervisord&lt;/a&gt; instance.&lt;/p&gt;
&lt;p&gt;So I was interested in some software that would let me manage multiple
&lt;a href=&quot;http://supervisord.org/index.html&quot;&gt;supervisord&lt;/a&gt; instances in a single page.  Turns out that there
are several tools lsted &lt;a href=&quot;http://supervisord.org/plugins.html#dashboards-and-tools-for-multiple-supervisor-instances&quot;&gt;here&lt;/a&gt;.  Unfortunately none of these
worked for me.&lt;/p&gt;
&lt;p&gt;So with the power of open source I rolled my own.  I based mine on &lt;a href=&quot;https://github.com/Tabcorp/supervisorui/&quot;&gt;supervisorui&lt;/a&gt;.
Unfortunately, &lt;a href=&quot;https://github.com/Tabcorp/supervisorui/&quot;&gt;supervisorui&lt;/a&gt; hasn&#039;t been updated in 10 years.  What is worse
it depends on a &lt;code&gt;Silex&lt;/code&gt; library that doesn&#039;t seem to exist anymore.&lt;/p&gt;
&lt;p&gt;So I ripped out a bunch of complex functionality and recreated it as
&lt;a href=&quot;https://github.com/TortugaLabs/supervisorui-redone&quot;&gt;supervisorui-redone&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/TortugaLabs/supervisorui-redone/main/img/screenshot.png&quot; alt=&quot;screenshot&quot; /&gt;&lt;/p&gt;
&lt;p&gt;It is indeed a quick and dirty implementation, unlike the original
&lt;a href=&quot;https://github.com/Tabcorp/supervisorui/&quot;&gt;supervisorui&lt;/a&gt; that has heavier JavaScript dependencies.
&lt;a href=&quot;https://github.com/Tabcorp/supervisorui/&quot;&gt;supervisorui&lt;/a&gt; is mostly a JavaScript applications and simply
uses &lt;code&gt;php&lt;/code&gt; for backend access to the &lt;a href=&quot;http://supervisord.org/index.html&quot;&gt;supervisord&lt;/a&gt;.  I assume
that it would make it more interactive.&lt;/p&gt;
&lt;p&gt;My re-worked version is basically a PHP application.&lt;/p&gt;
&lt;p&gt;As such, I removed the dependancies on&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://silex.sensiolabs.org/&quot;&gt;Silex&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://twitter.github.com/bootstrap/&quot;&gt;Twitter Bootstrap&lt;/a&gt; javascript, only
CSS is in use.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://jquery.com/&quot;&gt;jQuery&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://documentcloud.github.com/backbone/&quot;&gt;Backbone.js&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Also fixed &lt;a href=&quot;http://scripts.incutio.com/xmlrpc/&quot;&gt;Incutio XML-RPC Library&lt;/a&gt;
so that it works with PHP8.&lt;/p&gt;</content>
</entry>
<entry>
<title>voidlinux virtualization</title>
<link href="https://www.0ink.net/posts/2022/2022-08-14-void-kvm.html"></link>
<id>urn:uuid:d473bb2f-5b8e-a4e6-d77f-1229d97eb0c7</id>
<updated>2024-02-02T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This recipe is for setting virtualization on a voidlinux desktop.
Use this setup script to set things up on void linux.
Connecting to libvirtd
Note that virsh and virt-manager commands connect to different libvirtd
sessions by defauult.
virsh defaults to qemu:///session while virt-manager to qemu:///system.
...]]></summary>
<content type="html">&lt;p&gt;This recipe is for setting virtualization on a voidlinux desktop.&lt;/p&gt;
&lt;p&gt;Use this &lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/snippets/2022/void-kvm/setup.sh&quot;&gt;setup script&lt;/a&gt; to set things up on void linux.&lt;/p&gt;
&lt;h2 id=&quot;Connecting+to+libvirtd&quot; name=&quot;Connecting+to+libvirtd&quot;&gt;Connecting to libvirtd&lt;/h2&gt;
&lt;p&gt;Note that &lt;code&gt;virsh&lt;/code&gt; and &lt;code&gt;virt-manager&lt;/code&gt; commands connect to different &lt;code&gt;libvirtd&lt;/code&gt;
sessions by defauult.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;virsh&lt;/code&gt; defaults to &lt;code&gt;qemu:///session&lt;/code&gt; while &lt;code&gt;virt-manager&lt;/code&gt; to &lt;code&gt;qemu:///system&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It is better to use &lt;code&gt;qemu:///system&lt;/code&gt; as &lt;code&gt;qemu:///session&lt;/code&gt; does not seem to see
all available resources.&lt;/p&gt;
&lt;p&gt;To force &lt;code&gt;virsh&lt;/code&gt; to connect to the right session you can use commands such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;virsh --connect qemu:///system net-list
virsh --connect qemu:///system pool-list&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Create+network&quot; name=&quot;Create+network&quot;&gt;Create network&lt;/h2&gt;
&lt;p&gt;Set-up a bridge for internal networking using &lt;code&gt;NetworkManager&lt;/code&gt;.  See this &lt;a href=&quot;https://www.happyassassin.net/posts/2014/07/23/bridged-networking-for-libvirt-with-networkmanager-2014-fedora-21/&quot;&gt;article&lt;/a&gt;
for reference.&lt;/p&gt;
&lt;p&gt;Go to &lt;code&gt;NetworkManager&lt;/code&gt; menu and use the &lt;code&gt;Edit network connections&lt;/code&gt; applet:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;add new bridge connection&lt;/li&gt;
&lt;li&gt;give a suitable name&lt;/li&gt;
&lt;li&gt;disable IPv4 and IPv6&lt;/li&gt;
&lt;li&gt;everything can be left as default.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Import+images&quot; name=&quot;Import+images&quot;&gt;Import images&lt;/h2&gt;
&lt;p&gt;This needs to be done on CLI (as the GUI doesn&#039;t seem to allow this)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;virsh --connect qemu:///system vol-create-as $pool $vol 32k --format $format
virsh --connect qemu:///system vol-upload $vol $file&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For &lt;code&gt;format&lt;/code&gt;, use &lt;code&gt;raw&lt;/code&gt; for iso, &lt;code&gt;qcow2&lt;/code&gt; for actual drives.&lt;/p&gt;
&lt;h2 id=&quot;Setup+VM&quot; name=&quot;Setup+VM&quot;&gt;Setup VM&lt;/h2&gt;
&lt;p&gt;Just create VM as normal.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;virt-manager&lt;/code&gt; create Vm wizard&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;manual install&lt;/li&gt;
&lt;li&gt;alpine linux&lt;/li&gt;
&lt;li&gt;mem: depends&lt;/li&gt;
&lt;li&gt;Create storage (4GB is enough)&lt;/li&gt;
&lt;li&gt;name.  Customize configuration.  Network use NAT.&lt;/li&gt;
&lt;li&gt;Add CDROM, make readonly and shareable&lt;/li&gt;
&lt;li&gt;Add Network connected to internal bridge.&lt;/li&gt;
&lt;li&gt;Add boot device.&lt;/li&gt;
&lt;li&gt;Add shared filesystem:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;driver: virtio-9p&lt;/li&gt;
&lt;li&gt;source path: &lt;code&gt;/var/lib/libvirt/filesystems/shared&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;target path: &lt;code&gt;/shared&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Prepare system&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;create filesystem&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;mkfs.vfat /dev/vda&lt;/li&gt;
&lt;li&gt;apk add syslinux&lt;/li&gt;
&lt;li&gt;syslinux /dev/vda&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;copy media&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;mount -t vfat /dev/vda /mnt&lt;/li&gt;
&lt;li&gt;cp -av /media/cdrom/. /mnt&lt;/li&gt;
&lt;li&gt;edit /mnt/boot/syslinux/syslinux.cfg
add: dom0_mem=1024M&lt;/li&gt;
&lt;li&gt;umount /mnt&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;remove cdrom&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;power off&lt;/li&gt;
&lt;li&gt;remove cdrom&lt;/li&gt;
&lt;li&gt;change boot options&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;re-start system&lt;/li&gt;
&lt;li&gt;setup-alpine&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;enter fqdn&lt;/li&gt;
&lt;li&gt;set interface eth0 to dhcp&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;6&quot;&gt;
&lt;li&gt;Mount shared fs:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;mount -t 9p -o trans=virtio /shared /shared&lt;/li&gt;
&lt;li&gt;fstab&lt;/li&gt;
&lt;li&gt;/sharepoint   /share    9p  trans=virtio,version=9p2000.L,rw    0   0&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Thin+provisioning&quot; name=&quot;Thin+provisioning&quot;&gt;Thin provisioning&lt;/h2&gt;
&lt;p&gt;Create an overlay file like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;qemu-img create -b ubuntu-20.04-server-cloudimg-amd64-disk-kvm.img -F qcow2 -f qcow2 guest-1.qcow2&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>A couple of useful sites for development</title>
<link href="https://www.0ink.net/posts/2022/2022-05-29-websites.html"></link>
<id>urn:uuid:1e8d2471-41de-9fa3-9d7b-172685605312</id>
<updated>2023-04-03T00:00:00+02:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Unicode
Can be useful for looking up unicode code points.  Particularly
useful for looking up accented characters.  Another interesting
use is for UI graphics characters.
Unicode search
Another site to search for unicode characters.
...]]></summary>
<content type="html">&lt;h2 id=&quot;Unicode&quot; name=&quot;Unicode&quot;&gt;&lt;a href=&quot;https://www.compart.com/en/unicode/&quot;&gt;Unicode&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Can be useful for looking up &lt;code&gt;unicode&lt;/code&gt; code points.  Particularly
useful for looking up accented characters.  Another interesting
use is for UI graphics characters.&lt;/p&gt;
&lt;h2 id=&quot;Unicode+search&quot; name=&quot;Unicode+search&quot;&gt;&lt;a href=&quot;http://xahlee.info/comp/unicode_index.html&quot;&gt;Unicode search&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Another site to search for unicode characters.&lt;/p&gt;
&lt;h2 id=&quot;gist-hkan&quot; name=&quot;gist-hkan&quot;&gt;&lt;a href=&quot;https://gist.github.com/hkan/264423ab0ee720efb55e05a0f5f90887&quot;&gt;gist-hkan&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This can be used to look-up emojis and shortcode for emojis.&lt;/p&gt;
&lt;h2 id=&quot;color+picker&quot; name=&quot;color+picker&quot;&gt;&lt;a href=&quot;https://www.w3schools.com/colors/colors_picker.asp&quot;&gt;color picker&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Let you visually select a color so that you can paste
its definition in your HTML or CSS documents&lt;/p&gt;
&lt;h2 id=&quot;color+names&quot; name=&quot;color+names&quot;&gt;&lt;a href=&quot;https://www.w3schools.com/colors/colors_names.asp&quot;&gt;color names&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This is a list of &amp;quot;standard&amp;quot; color names.&lt;/p&gt;</content>
</entry>
<entry>
<title>Keyboard Mouse control</title>
<link href="https://www.0ink.net/posts/2022/2022-02-13-keyboard_mouse.html"></link>
<id>urn:uuid:0e814e32-a136-e603-5a56-1d55073a66b3</id>
<updated>2022-03-03T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This comes in handy when working at a colo or someplace where you
don't have a mouse and then find yourself needing to use X11. Press
the following key combo:
Ctrl-Shift-Numlock
Now you can control the mouse pointer using the number pad.  The
key bindings are:
...]]></summary>
<content type="html">&lt;p&gt;This comes in handy when working at a &lt;strong&gt;colo&lt;/strong&gt; or someplace where you
don&#039;t have a mouse and then find yourself needing to use &lt;em&gt;X11&lt;/em&gt;. Press
the following key combo:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Ctrl-Shift-Numlock&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you can control the mouse pointer using the number pad.  The
key bindings are:&lt;/p&gt;
&lt;h2 id=&quot;Move+the+mouse+pointer&quot; name=&quot;Move+the+mouse+pointer&quot;&gt;Move the mouse pointer&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;7, 8, 9 are the up directions&lt;/li&gt;
&lt;li&gt;4, 6 are left and right&lt;/li&gt;
&lt;li&gt;1, 2, 3 are the down directions&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;To+control+the+mouse+buttons&quot; name=&quot;To+control+the+mouse+buttons&quot;&gt;To control the mouse buttons&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/&lt;/code&gt; selects the left mouse button&lt;/li&gt;
&lt;li&gt;&lt;code&gt;*&lt;/code&gt; selects the middle mouse button&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-&lt;/code&gt; selects the right mouse button&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This only selects the mouse button but does not press it.  To
actually use the mouse button:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;5&lt;/code&gt; mouse click&lt;/li&gt;
&lt;li&gt;&lt;code&gt;+&lt;/code&gt; double mouse click&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0&lt;/code&gt; to press and hold the mouse button (e.g. for dragging)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.&lt;/code&gt; to release the currently mouse button.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, to do a mouse-click with the middle button you
first press the &lt;code&gt;*&lt;/code&gt; key and then &lt;code&gt;5&lt;/code&gt; to click.&lt;/p&gt;</content>
</entry>
<entry>
<title>flatpak</title>
<link href="https://www.0ink.net/posts/2022/2022-02-08-flatpak.html"></link>
<id>urn:uuid:5eccd25b-a75e-4650-343a-16e71cb041c3</id>
<updated>2022-03-03T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Flatpak is a utility for software deployment and package management for
Linux. It is advertised as offering a sandbox environment in which users
can run application software in isolation from the rest of the system.
Flatpak was developed as part of the freedesktop.org project and was
originally called xdg-app.
Snap vs Flatpak
...]]></summary>
<content type="html">&lt;p&gt;Flatpak is a utility for software deployment and package management for
Linux. It is advertised as offering a sandbox environment in which users
can run application software in isolation from the rest of the system.
Flatpak was developed as part of the freedesktop.org project and was
originally called xdg-app.&lt;/p&gt;
&lt;h2 id=&quot;Snap+vs+Flatpak&quot; name=&quot;Snap+vs+Flatpak&quot;&gt;Snap vs Flatpak&lt;/h2&gt;
&lt;p&gt;Snaps and Flatpaks are often compared to each other because they both
make it super easy for Linux users to get the latest versions of
desktop applications. If a Linux user wants to install the latest
version of apps like Slack, Krita or Blender, either tool will work
just fine. There is one fundamental difference between Snaps and
Flatpaks, however. While both are systems for distributing Linux apps,
snap is also a tool to build Linux Distributions.&lt;/p&gt;
&lt;p&gt;Flatpak is designed to install and update “apps”; user-facing software
such as video editors, chat programs and more. Your operating system,
however, contains a lot more software than apps. It contains a kernel,
printer drivers, audio subsystems and more. While Flatpak assumes this
software is installed using a traditional package manager, snaps can
install anything. These are some examples.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There is current work ongoing to put the entire Linux printing stack
inside of a snap. This has the advantage that printer drivers can be
updated independently from the operating system. Once this work is
complete, every single Ubuntu version will be able to use the latest
printer drivers. Trying to use new printers on old Linux distributions
can be very frustrating, and installing newer printer drivers can be
risky. Having the printing stack in a snap will solve this issue.&lt;/li&gt;
&lt;li&gt;A few years ago, Ubuntu drastically changed the system theme. When
the &amp;quot;CommuniTheme&amp;quot; initiative started, they wanted an easy way to
make the latest updates of the theme available to users immediately.
Normally, a system theme is shipped together with the distro, so
users do not get theme updates after the distro releases. For
“CommuniTheme”, however, they fixed this by putting the system
theme inside of a snap. Because of this, users got updates to their
theme every day, instead of every 6 months. This is again not
something Flatpak was built for. Flatpak applications can update
their own theme, but it is not possible to ship the system theme
as a Flatpak. This is because Flatpak was designed for distributing
apps, not building an entire Linux distribution.&lt;/li&gt;
&lt;li&gt;Even the Linux kernel, the most fundamental part of a Linux
distribution, can be put in a snap. This is used a lot for IoT
devices such as routers and satellites. The impact of a broken
kernel update is catastrophic if you require a rocket in order
to plug a USB stick into the device. Snaps allow these devices
to safely update their kernel and automatically roll back if
something goes wrong during the process.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As a result, it’s possible to build an entire operating system using
only snaps, which is exactly what Ubuntu Core is.&lt;/p&gt;
&lt;p&gt;Flatpak was designed to give developers an easy way to bring their
apps directly to users, and it does that job very well. The focused
approach of Flatpak even has a big advantage: it’s a lot easier for
a distribution to integrate with Flatpak because it does a lot less.
The tradeoff is that it only provides app distribution; it doesn’t
solve the issues of distributing entire operating systems. Fedora
Silverblue, for example, creates an immutable desktop operating
system by using Flatpak for app distribution and OSTree
for distributing the OS itself.&lt;/p&gt;
&lt;p&gt;Unfortunately, for my choice Linux distro (&lt;a href=&quot;https://voidlinux.org/&quot;&gt;void linux&lt;/a&gt;), only Flatpak is
available.&lt;/p&gt;
&lt;h2 id=&quot;Install+Flatpak&quot; name=&quot;Install+Flatpak&quot;&gt;Install Flatpak&lt;/h2&gt;
&lt;p&gt;To install Flatpak, run the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo xbps-install -S flatpak
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;After+installation&quot; name=&quot;After+installation&quot;&gt;After installation&lt;/h2&gt;
&lt;p&gt;Once flatpak is installed, you can choose to install software system
wide or on an per-user basis.  The default is to install system wide
which requires root priviledge (or &lt;code&gt;sudo&lt;/code&gt;).  You may also specify
&lt;code&gt;--system&lt;/code&gt; if you want to force system wide install.&lt;/p&gt;
&lt;p&gt;To manage software on a per-user basis, use the &lt;code&gt;--user&lt;/code&gt; option.&lt;/p&gt;
&lt;p&gt;The best place to find software for Flatpak is &lt;a href=&quot;https://flathub.org&quot;&gt;flathub&lt;/a&gt;.
You can browse the catalogue in &lt;a href=&quot;https://flathub.org&quot;&gt;https://flathub.org&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you want to start installing software you need to first add the remote:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or for per-user installation:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flatpak --user remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Afterwards you can install software:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo flatpak install pkg-id ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or per-user:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flatpak --user install pkg-id ...
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Interesting+flatpaks%3A&quot; name=&quot;Interesting+flatpaks%3A&quot;&gt;Interesting flatpaks:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://flathub.org/apps/details/com.spotify.Client&quot;&gt;com.spotify.Client&lt;/a&gt; : Spotify desktop client&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://flathub.org/apps/details/dev.alextren.Spot&quot;&gt;dev.alextren.Spot&lt;/a&gt; : Alternative spotify client&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://flathub.org/apps/details/org.chromium.Chromium&quot;&gt;org.chromium.Chromium&lt;/a&gt; : Open source chrome.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://flathub.org/apps/details/org.mozilla.firefox&quot;&gt;org.mozilla.firefox&lt;/a&gt; : Firefox Web browser&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://flathub.org/apps/details/com.valvesoftware.Steam&quot;&gt;com.valvesoftware.Steam&lt;/a&gt; : Steam Launcher&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://flathub.org/apps/details/com.simplenote.Simplenote&quot;&gt;com.simplenote.Simplenote&lt;/a&gt; : Simple Note&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://flathub.org/apps/details/com.mojang.Minecraft&quot;&gt;com.mojang.Minecraft&lt;/a&gt; : Minecraft launcher&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Basic+commands&quot; name=&quot;Basic+commands&quot;&gt;Basic commands&lt;/h2&gt;
&lt;p&gt;Search:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flatpak search gimp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Install:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo flatpak install flathub org.gimp.GIMP
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flatpak --user install flathub org.gimp.GIMP
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that the current version of flatpak will do a search, so you don&#039;t
have to specify the ID.&lt;/p&gt;
&lt;p&gt;Running applications:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flatpak run org.gimp.GIMP
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will automatically determine if the application was installed
system-wide or on per-user basis.  You can use the &lt;code&gt;--system&lt;/code&gt; or
&lt;code&gt;--user&lt;/code&gt; flags to specify which to use if it was installed multiple
times, otherwise the system-wide installation will be used.&lt;/p&gt;
&lt;p&gt;Updating:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo flatpak update
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flatpak --user update
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will update all installed applications.&lt;/p&gt;
&lt;p&gt;List installed applications:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flatpak list
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will list applications and runtimes indicating if it was installed
system wide or per-user.  To list only applications:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flatpak list --app
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Removing applications:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo flatpak uninstall org.gimp.GIMP
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flatpak --user uninstall org.gimp.GIMP
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Keep in mind that uninstalling applications will not delete files in
your $HOME directory.  These are in &lt;code&gt;$HOME/.var/app/$PKGID&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You need to manually delete these files.&lt;/p&gt;
&lt;h2 id=&quot;Managing+repositories&quot; name=&quot;Managing+repositories&quot;&gt;Managing repositories&lt;/h2&gt;
&lt;p&gt;List remotes:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flatpak remotes&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This gives a list of the existing remotes that have been added. The
list indicates whether each remote has been added per-user or
system-wide.&lt;/p&gt;
&lt;p&gt;Add a remote:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;flatpak --user remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Remove a remote:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo flatpak remote-delete flathub
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flatpak --user remote-delete flathub
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Troubleshooting&quot; name=&quot;Troubleshooting&quot;&gt;Troubleshooting&lt;/h2&gt;
&lt;p&gt;Flatpak has a few commands that can help you to get things working
again when something goes wrong.&lt;/p&gt;
&lt;p&gt;To remove runtimes and extensions that are not used by installed
applications, use:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo flatpak uninstall --unused
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flatpak --user uninstall --unused
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To fix inconsistencies with your local installation, use:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo flatpak repair
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flatpak --user repair
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Flatpak also has a number of commands to manage the portal
permissions of installed apps. To reset all portal permissions
for an app, use flatpak permission-reset:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo flatpak permission-reset org.gimp.GIMP
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flatpak --user permission-reset org.gimp.GIMP
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To find out what changes have been made to your Flatpak installation
over time, you can take a look at the logs (since 1.2):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flatpak history
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, this requires &lt;code&gt;libsystemd&lt;/code&gt; which is not used in void linux.&lt;/p&gt;</content>
</entry>
<entry>
<title>My git release script</title>
<link href="https://www.0ink.net/posts/2022/2022-01-12-ghrelease.html"></link>
<id>urn:uuid:f399e4fa-f3bc-c395-cab8-236d952e6740</id>
<updated>2022-01-16T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[I always had issues remembering how to create releases.
So in order to standardise things, I wrote this script:

ghrelease

So whenever I am ready to release I would then just issue
...]]></summary>
<content type="html">&lt;p&gt;I always had issues remembering how to create releases.
So in order to standardise things, I wrote this script:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/TortugaLabs/my-gh-tools/blob/main/ghrelease.sh&quot;&gt;ghrelease&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So whenever I am ready to release I would then just issue
the command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;./ghrelease vX.Y.Z&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Pre-requisistes%3A&quot; name=&quot;Pre-requisistes%3A&quot;&gt;Pre-requisistes:&lt;/h2&gt;
&lt;p&gt;You obviously need &lt;code&gt;git&lt;/code&gt;.  But also you would need
&lt;a href=&quot;https://github.com/cli/cli&quot;&gt;github-cli&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Your repository must also be &lt;em&gt;clean&lt;/em&gt;, without any
pending commits.&lt;/p&gt;
&lt;p&gt;You must be on the &lt;em&gt;default&lt;/em&gt; branch (usually &lt;code&gt;main&lt;/code&gt; or
&lt;code&gt;master&lt;/code&gt;), unless doing a &lt;strong&gt;pre-release&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Optionally, you may have a &lt;code&gt;wfscripts/checks&lt;/code&gt; directory
containing checking scripts.&lt;/p&gt;
&lt;h2 id=&quot;What+happens+on+release&quot; name=&quot;What+happens+on+release&quot;&gt;What happens on release&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;remote &lt;code&gt;--tags&lt;/code&gt; will be synchronised with local tags.&lt;/li&gt;
&lt;li&gt;if a tag of the same name already exists then, release
will be stopped.&lt;/li&gt;
&lt;li&gt;check if we are on the &lt;code&gt;default&lt;/code&gt; branch (unless pre-release)&lt;/li&gt;
&lt;li&gt;check if there are any uncomitted changes.&lt;/li&gt;
&lt;li&gt;If available, &lt;code&gt;wfscripts/checks&lt;/code&gt; is run using &lt;code&gt;run-parts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Will create release notes based on log entries since the
last release (previous annotated tagged commit)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;VERSION&lt;/code&gt; or &lt;code&gt;version.h&lt;/code&gt; is updated and comitted.&lt;/li&gt;
&lt;li&gt;A new annotated tag is created.&lt;/li&gt;
&lt;li&gt;Commits and tags are push to remote (origin).&lt;/li&gt;
&lt;li&gt;Release is created using &lt;code&gt;github-cli&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;Pre-releases&quot; name=&quot;Pre-releases&quot;&gt;Pre-releases&lt;/h2&gt;
&lt;p&gt;You can create pre-releases.  These do not have to be on the
&lt;em&gt;default&lt;/em&gt; branch.  To do this use the &lt;code&gt;--rc&lt;/code&gt; (Release candidate)
option:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;./ghrelease vX.Y.Z-rcN&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will create a release in &lt;code&gt;github&lt;/code&gt; but tag it as &lt;strong&gt;pre-release&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;After release, you may delete all pre-release candidates:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;./ghrelease --purge&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>Meta Database</title>
<link href="https://www.0ink.net/posts/2022/2022-01-10-meta_db.html"></link>
<id>urn:uuid:af84b35f-1414-ba08-1a2c-57389ac07d21</id>
<updated>2022-01-10T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[So I was looking for a way to version control database schemas,
but I never found something that worked for me.  I found all
these options that never seem to match what I wanted:

metabase
dbv
...]]></summary>
<content type="html">&lt;p&gt;So I was looking for a way to version control database schemas,
but I never found something that worked for me.  I found all
these options that never seem to match what I wanted:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://freecode.com/projects/metabase&quot;&gt;metabase&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/victorstanciu/dbv&quot;&gt;dbv&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://deltasql.sourceforge.net/features.php&quot;&gt;delta sql&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://propelorm.org/&quot;&gt;Propel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://phinx.org/&quot;&gt;phinx&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.doctrine-project.org/&quot;&gt;doctrine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://redbeanphp.com/index.php&quot;&gt;redbeanphp&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At the end, I finally began doing the following.&lt;/p&gt;
&lt;p&gt;The first schema release, I would create a file called:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;init-1.0.sql&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This would contain all the sql statements needed to initialize
the database.  To indicate this is the current version I would
create a symlink to it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;init.sql -&amp;gt; init-1.0.sql&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The next schema update I would create file with the commands
to transform the schema.  i.e. additional &lt;code&gt;CREATE DATABASE&lt;/code&gt; or
&lt;code&gt;ALTER TABLE&lt;/code&gt; etc.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;upgrade-1.1.sql&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So actually, the &lt;code&gt;upgrade&lt;/code&gt; file would be the working version
used for development.  For release then I would create a
new &lt;code&gt;init&lt;/code&gt; file.  Either doing it manually or by doing:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cat init-1.0.sql upgrade-1.1.sql &amp;gt; init-1.1.sql&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To indicate that this is the current version, the symlink would
be updated:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;init.sql -&amp;gt; init-1.1.sql&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Final+notes&quot; name=&quot;Final+notes&quot;&gt;Final notes&lt;/h2&gt;
&lt;p&gt;This would end-up with a history of schema changes and scripts that
could be used to upgrade the database schema from any version to
any later version.&lt;/p&gt;</content>
</entry>
<entry>
<title>lnbin</title>
<link href="https://www.0ink.net/posts/2022/2022-01-10-lnbin.html"></link>
<id>urn:uuid:51552e6a-26df-0f25-c998-309b5a7d10a9</id>
<updated>2024-06-03T00:00:00+02:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This is my lnbin script.
This is a program for managing symlink in a /usr/local/bin
directory.  It is similar to stow, lndir, cleanlinks and
others.
The approach used by lnbin is based on Stow, and it is to install
each into its own tree, then use symbolic links to make its bin
...]]></summary>
<content type="html">&lt;p&gt;This is my &lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/snippets/2022/lnbin&quot;&gt;lnbin&lt;/a&gt; script.&lt;/p&gt;
&lt;p&gt;This is a program for managing symlink in a &lt;code&gt;/usr/local/bin&lt;/code&gt;
directory.  It is similar to stow, lndir, cleanlinks and
others.&lt;/p&gt;
&lt;p&gt;The approach used by &lt;em&gt;lnbin&lt;/em&gt; is based on Stow, and it is to install
each into its own tree, then use symbolic links to make its bin
directory, so that the command can be in the executable path.&lt;/p&gt;
&lt;p&gt;When run, &lt;em&gt;lnbin&lt;/em&gt; examines packages in &lt;code&gt;pkgs-dir&lt;/code&gt; and the
&lt;code&gt;target&lt;/code&gt; directory (see OPTIONS), adding or removing links as
needed.&lt;/p&gt;
&lt;h2 id=&quot;Sample+usage%3A&quot; name=&quot;Sample+usage%3A&quot;&gt;Sample usage:&lt;/h2&gt;
&lt;h3 id=&quot;pkg+installation&quot; name=&quot;pkg+installation&quot;&gt;pkg installation&lt;/h3&gt;
&lt;p&gt;The standard way to use &lt;em&gt;lnbin&lt;/em&gt; is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;download source package&lt;/li&gt;
&lt;li&gt;build and install package&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# extract archive
tar zxvf archive-x.x.tar.gz
cd archive-x.x
# GNU autoconf
./configure --prefix=&quot;/usr/local/pkgs/archive-x.x&quot;
make
# Package installation
make install
# ... or ...
make install DESTDIR=/usr/local/pkgs/archive-x.x&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;update symlinks in /usr/local/bin&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd /usr/local/bin
lnbin -v -x ../pkgs&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will add the new links (and also remove/update obsolete/changed links)&lt;/p&gt;
&lt;h3 id=&quot;Removing+packages&quot; name=&quot;Removing+packages&quot;&gt;Removing packages&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;rm -rf /usr/local/pkgs/archive-x.x
cd /usr/local/bin
lnbin -v -x ../pkgs&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Updating+symlinks+%28after+upgrade%29&quot; name=&quot;Updating+symlinks+%28after+upgrade%29&quot;&gt;Updating symlinks (after upgrade)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;cd /usr/local/bin
lnbin -v -x ../pkgs&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will add new links and/or remove obsolete links&lt;/p&gt;
&lt;h2 id=&quot;Handling+additional+files&quot; name=&quot;Handling+additional+files&quot;&gt;Handling additional files&lt;/h2&gt;
&lt;p&gt;For packages that install additional files like man pages
or desktop files you can use the commands:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;lnbin -v -x -s share/man/man1 -t /usr/local/share/man/man1 ../../../pkgs
lnbin -v -x -s share/applications -t /usr/local/share/applications ../../pkgs&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;References&quot; name=&quot;References&quot;&gt;References&lt;/h2&gt;
&lt;p&gt;There are a number of packages that do similar things.  The main
attractiveness of this one is that it is a &lt;code&gt;/bin/sh&lt;/code&gt; script
intended to have low dependancies.&lt;/p&gt;
&lt;p&gt;Other options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.gnu.org/software/stow/&quot;&gt;stow: a perl program&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.rpcurnow.force9.co.uk/spill/&quot;&gt;spill: written C, so also has a low dependency count.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.wyrick.org/source/perl/stash/&quot;&gt;stash: a package manager for non-root users (in perl)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://mvertes.free.fr/lt/lt&quot;&gt;lt: shell script by Marc Vertes.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I chose not to use &lt;em&gt;lt&lt;/em&gt; because while written in &lt;code&gt;/bin/sh&lt;/code&gt;, I wanted
a script that could use relative links instead of absolute links.&lt;/p&gt;</content>
</entry>
<entry>
<title>Linux Icons</title>
<link href="https://www.0ink.net/posts/2022/2022-01-10-linux_icons.html"></link>
<id>urn:uuid:a76da165-9f8f-404d-cca6-b764d73770c4</id>
<updated>2022-01-16T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[A quick note on how to add icons to menus in a Linux desktop.

Create the icon image in: /usr/share/pixmaps.

png and svg (and maybe others) are supported.
24x24 seems to be a good size for menus.
...]]></summary>
<content type="html">&lt;p&gt;A quick note on how to add icons to menus in a Linux desktop.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create the icon image in: &lt;code&gt;/usr/share/pixmaps&lt;/code&gt;.
&lt;ul&gt;
&lt;li&gt;png and svg (and maybe others) are supported.&lt;/li&gt;
&lt;li&gt;24x24 seems to be a good size for menus.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;You need to create a &lt;code&gt;.desktop&lt;/code&gt; file in &lt;code&gt;/usr/share/applications&lt;/code&gt;.
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://specifications.freedesktop.org/menu-spec/latest/index.html&quot;&gt;Desktop menu specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://specifications.freedesktop.org/menu-spec/latest/apa.html&quot;&gt;Registered categories&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;User desktop files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;These are located in:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$HOME/.local/share/applications&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Icons can be found here:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$HOME/.local/icons&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I am not sure about this one&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Autostart files:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$HOME/.config/autostart&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See also: &lt;a href=&quot;https://wiki.archlinux.org/title/desktop_entries&quot;&gt;archlinux wiki&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;There is a command line utility:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://linux.die.net/man/1/xdg-desktop-menu&quot;&gt;xdg-desktop-menu&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>nas ops cmd</title>
<link href="https://www.0ink.net/posts/2022/2022-01-05-ops.html"></link>
<id>urn:uuid:52f4adc5-bdea-18d7-6c9a-b733dd637ddb</id>
<updated>2024-02-02T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This is my op script.
This is stupidly simple script to elevate priviledges in order to
manage NFS shares on my QNAP NAS.
The idea is that NFS shares do squash-root so admin access is
disallowed through NFS.  This gives a convenient way to issue
root level commands without using NFS but instead use ssh
...]]></summary>
<content type="html">&lt;p&gt;This is my &lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/snippets/2022/opcmd&quot;&gt;op&lt;/a&gt; script.&lt;/p&gt;
&lt;p&gt;This is stupidly simple script to elevate priviledges in order to
manage NFS shares on my QNAP NAS.&lt;/p&gt;
&lt;p&gt;The idea is that NFS shares do &lt;code&gt;squash-root&lt;/code&gt; so admin access is
disallowed through NFS.  This gives a convenient way to issue
root level commands without using NFS but instead use &lt;code&gt;ssh&lt;/code&gt;
(and ssh authentication) to do this, which should provide
stronger security.&lt;/p&gt;
&lt;p&gt;This script makes the following assumptions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the user has a &lt;code&gt;ssh-key&lt;/code&gt; with admin access on the NAS.&lt;/li&gt;
&lt;li&gt;NFS is mounted using &lt;code&gt;autofs&lt;/code&gt; and is on the &lt;code&gt;/net&lt;/code&gt; virtual folder.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The way it works is that it uses the current working directory
when the command is launch.  It then resolves any symlink path and
check is the directory is in the &lt;code&gt;/net/&lt;/code&gt; virtual folder so the
NFS server is the second component of the path.&lt;/p&gt;</content>
</entry>
<entry>
<title>Linux stuff</title>
<link href="https://www.0ink.net/posts/2022/2022-01-04-linuxstuff.html"></link>
<id>urn:uuid:3257e522-8619-1809-f127-11b1206d1434</id>
<updated>2022-01-10T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Sudoers
Since sudo v1.9, it is possible to use the following
statements:

#includedir
@includedir
...]]></summary>
<content type="html">&lt;h2 id=&quot;Sudoers&quot; name=&quot;Sudoers&quot;&gt;Sudoers&lt;/h2&gt;
&lt;p&gt;Since &lt;a href=&quot;https://www.sudo.ws/&quot;&gt;sudo&lt;/a&gt; v1.9, it is possible to use the following
statements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;#includedir&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@includedir&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is useful better for adding sudo rules rather than modifying
the &lt;code&gt;/etc/sudoers&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;Make sure that the &lt;code&gt;includedir&lt;/code&gt; statement is the &lt;strong&gt;LAST&lt;/strong&gt; entry
in &lt;code&gt;/etc/sudoers&lt;/code&gt; and the files in the directory:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;names do not contain &lt;code&gt;.&lt;/code&gt; (dots)&lt;/li&gt;
&lt;li&gt;ownership to &lt;code&gt;root&lt;/code&gt;:&lt;code&gt;root&lt;/code&gt; and permission is set to &lt;code&gt;0440&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Graphviz markdown extensions</title>
<link href="https://www.0ink.net/posts/2022/2022-01-04-graphviz.html"></link>
<id>urn:uuid:c55b2186-f38c-68f5-0246-7ea696f37367</id>
<updated>2022-01-10T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[I have enabled several extensions to my pelican website.
One that I wanted to include was graphviz.  So, I searched
for one and while I found a few, they somehow, did not work for me.
So I wrote my own: mdx_graphviz.
It is quite straight forward.  You just need to create blocks:
dot {
...]]></summary>
<content type="html">&lt;p&gt;I have enabled several extensions to my &lt;a href=&quot;https://github.com/getpelican/pelican&quot;&gt;pelican&lt;/a&gt; website.&lt;/p&gt;
&lt;p&gt;One that I wanted to include was &lt;a href=&quot;https://graphviz.org/&quot;&gt;graphviz&lt;/a&gt;.  So, I searched
for one and while I found a few, they somehow, did not work for me.&lt;/p&gt;
&lt;p&gt;So I wrote my own: &lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/master/src/mdx/mdx_graphviz.py&quot;&gt;mdx_graphviz&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It is quite straight forward.  You just need to create blocks:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dot {
    digraph G {
        rankdir=LR
        Earth [peripheries=2]
        Mars
        Earth -&amp;gt; Mars
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can use this &lt;a href=&quot;http://magjac.com/graphviz-visual-editor/&quot;&gt;Graphviz Visual Editor&lt;/a&gt; for a more
interactive approach.&lt;/p&gt;</content>
</entry>
<entry>
<title>Pelican Test page</title>
<link href="https://www.0ink.net/posts/2021/2021-12-26-pelican_tests.html"></link>
<id>urn:uuid:b583a149-ccae-2045-d474-7c779cc99be4</id>
<updated>2024-02-02T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[
shortcodes
mytags
Drawings

aafigure
...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#shortcodes&quot;&gt;shortcodes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#mytags&quot;&gt;mytags&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Drawings&quot;&gt;Drawings&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#aafigure&quot;&gt;aafigure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#blockdiag&quot;&gt;blockdiag&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#mdx_include&quot;&gt;mdx_include&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#GFM+style+check+lists&quot;&gt;GFM style check lists&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#my+mdx+variables&quot;&gt;my mdx variables&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;p&gt;This page is used for testing some pelican and markdown extensions
I added.&lt;/p&gt;
&lt;h2 id=&quot;shortcodes&quot; name=&quot;shortcodes&quot;&gt;shortcodes&lt;/h2&gt;
&lt;p&gt;OK, this is awkward... I am not sure if this is needed.&lt;/p&gt;
&lt;h2 id=&quot;mytags&quot; name=&quot;mytags&quot;&gt;mytags&lt;/h2&gt;
&lt;p&gt;Using &lt;del&gt;del&lt;/del&gt; and &lt;ins&gt;ins&lt;/ins&gt;.&lt;/p&gt;
&lt;p&gt;test &lt;mark&gt;mark&lt;/mark&gt; tags.  How about E=mc&lt;sup&gt;2&lt;/sup&gt; and H&lt;sub&gt;2&lt;/sub&gt;O.&lt;/p&gt;
&lt;h2 id=&quot;Drawings&quot; name=&quot;Drawings&quot;&gt;Drawings&lt;/h2&gt;
&lt;h3 id=&quot;aafigure&quot; name=&quot;aafigure&quot;&gt;aafigure&lt;/h3&gt;
&lt;div&gt;&lt;svg class=&quot;bob&quot; font-family=&quot;arial&quot; font-size=&quot;14&quot; height=&quot;48&quot; width=&quot;312&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;defs&gt;
&lt;marker id=&quot;triangle&quot; markerHeight=&quot;10&quot; markerUnits=&quot;strokeWidth&quot; markerWidth=&quot;10&quot; orient=&quot;auto&quot; refX=&quot;15&quot; refY=&quot;10&quot; viewBox=&quot;0 0 50 20&quot;&gt;
&lt;path d=&quot;M 0 0 L 30 10 L 0 20 z&quot;/&gt;
&lt;/marker&gt;
&lt;/defs&gt;
&lt;style&gt;

    line, path {
      stroke: black;
      stroke-width: 2;
      stroke-opacity: 1;
      fill-opacity: 1;
      stroke-linecap: round;
      stroke-linejoin: miter;
    }
    circle {
      stroke: black;
      stroke-width: 2;
      stroke-opacity: 1;
      fill-opacity: 1;
      stroke-linecap: round;
      stroke-linejoin: miter;
    }
    circle.solid {
      fill:black;
    }
    circle.open {
      fill:transparent;
    }
    tspan.head{
        fill: none;
        stroke: none;
    }
    
&lt;/style&gt;
&lt;path d=&quot; M 4 8 L 8 8 M 4 8 L 4 16 M 8 8 L 16 8 M 8 8 L 16 8 L 24 8 M 16 8 L 24 8 L 32 8 M 24 8 L 32 8 L 40 8 M 32 8 L 40 8 L 48 8 M 40 8 L 48 8 L 56 8 M 48 8 L 56 8 L 64 8 M 56 8 L 64 8 L 72 8 M 64 8 L 72 8 L 80 8 M 72 8 L 80 8 M 84 8 L 80 8 M 84 8 L 84 16 M 116 8 L 120 8 M 116 8 L 116 16 M 120 8 L 128 8 M 120 8 L 128 8 L 136 8 M 128 8 L 136 8 L 144 8 M 136 8 L 144 8 L 152 8 M 144 8 L 152 8 L 160 8 M 152 8 L 160 8 L 168 8 M 160 8 L 168 8 M 172 8 L 168 8 M 172 8 L 172 16 M 204 8 L 208 8 M 204 8 L 204 16 M 208 8 L 216 8 M 208 8 L 216 8 L 224 8 M 216 8 L 224 8 L 232 8 M 224 8 L 232 8 L 240 8 M 232 8 L 240 8 L 248 8 M 240 8 L 248 8 L 256 8 M 248 8 L 256 8 L 264 8 M 256 8 L 264 8 L 272 8 M 264 8 L 272 8 L 280 8 M 272 8 L 280 8 L 288 8 M 280 8 L 288 8 L 296 8 M 288 8 L 296 8 L 304 8 M 296 8 L 304 8 M 308 8 L 304 8 M 308 8 L 308 16 M 4 16 L 4 32 M 4 16 L 4 32 M 88 24 L 96 24 M 88 24 L 96 24 L 104 24 M 96 24 L 104 24 L 112 24 M 104 24 L 112 24 M 176 24 L 184 24 M 176 24 L 184 24 L 192 24 M 184 24 L 192 24 L 200 24 M 192 24 L 200 24 M 308 16 L 308 32 M 308 16 L 308 32 M 4 40 L 4 32 M 4 40 L 8 40 L 16 40 M 8 40 L 16 40 L 24 40 M 16 40 L 24 40 L 32 40 M 24 40 L 32 40 L 40 40 M 32 40 L 40 40 L 48 40 M 40 40 L 48 40 L 56 40 M 48 40 L 56 40 L 64 40 M 56 40 L 64 40 L 72 40 M 64 40 L 72 40 L 80 40 M 72 40 L 80 40 M 84 40 L 84 32 M 84 40 L 80 40 M 116 40 L 116 32 M 116 40 L 120 40 L 128 40 M 120 40 L 128 40 L 136 40 M 128 40 L 136 40 L 144 40 M 136 40 L 144 40 L 152 40 M 144 40 L 152 40 L 160 40 M 152 40 L 160 40 L 168 40 M 160 40 L 168 40 M 172 40 L 172 32 M 172 40 L 168 40 M 204 40 L 204 32 M 204 40 L 208 40 L 216 40 M 208 40 L 216 40 L 224 40 M 216 40 L 224 40 L 232 40 M 224 40 L 232 40 L 240 40 M 232 40 L 240 40 L 248 40 M 240 40 L 248 40 L 256 40 M 248 40 L 256 40 L 264 40 M 256 40 L 264 40 L 272 40 M 264 40 L 272 40 L 280 40 M 272 40 L 280 40 L 288 40 M 280 40 L 288 40 L 296 40 M 288 40 L 296 40 L 304 40 M 296 40 L 304 40 M 308 40 L 308 32 M 308 40 L 304 40&quot; fill=&quot;none&quot;/&gt;
&lt;path d=&quot;&quot; fill=&quot;none&quot; stroke-dasharray=&quot;3 3&quot;/&gt;
&lt;text x=&quot;9&quot; y=&quot;28&quot;&gt;
KPN
&lt;/text&gt;
&lt;text x=&quot;41&quot; y=&quot;28&quot;&gt;
modem+
&lt;/text&gt;
&lt;text x=&quot;113&quot; y=&quot;28&quot;&gt;
+router+
&lt;/text&gt;
&lt;text x=&quot;201&quot; y=&quot;28&quot;&gt;
+HOME
&lt;/text&gt;
&lt;text x=&quot;249&quot; y=&quot;28&quot;&gt;
NETWORK
&lt;/text&gt;
&lt;/svg&gt;
&lt;/div&gt;
&lt;div&gt;&lt;svg class=&quot;bob&quot; font-family=&quot;arial&quot; font-size=&quot;14&quot; height=&quot;208&quot; width=&quot;168&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;defs&gt;
&lt;marker id=&quot;triangle&quot; markerHeight=&quot;10&quot; markerUnits=&quot;strokeWidth&quot; markerWidth=&quot;10&quot; orient=&quot;auto&quot; refX=&quot;15&quot; refY=&quot;10&quot; viewBox=&quot;0 0 50 20&quot;&gt;
&lt;path d=&quot;M 0 0 L 30 10 L 0 20 z&quot;/&gt;
&lt;/marker&gt;
&lt;/defs&gt;
&lt;style&gt;

    line, path {
      stroke: black;
      stroke-width: 2;
      stroke-opacity: 1;
      fill-opacity: 1;
      stroke-linecap: round;
      stroke-linejoin: miter;
    }
    circle {
      stroke: black;
      stroke-width: 2;
      stroke-opacity: 1;
      fill-opacity: 1;
      stroke-linecap: round;
      stroke-linejoin: miter;
    }
    circle.solid {
      fill:black;
    }
    circle.open {
      fill:transparent;
    }
    tspan.head{
        fill: none;
        stroke: none;
    }
    
&lt;/style&gt;
&lt;path d=&quot; M 52 8 L 56 8 M 52 8 L 52 16 M 56 8 L 64 8 M 56 8 L 64 8 L 72 8 M 64 8 L 72 8 L 80 8 M 72 8 L 80 8 L 88 8 M 80 8 L 88 8 L 96 8 M 88 8 L 96 8 M 100 8 L 96 8 M 100 8 L 100 16 M 52 16 L 52 32 M 52 16 L 52 32 M 100 16 L 100 32 M 100 16 L 100 32 M 132 16 L 132 32 M 132 16 L 132 32 M 16 40 L 24 40 M 16 40 L 24 40 L 32 40 M 24 40 L 32 40 M 52 40 L 52 32 M 52 40 L 48 40 M 52 40 L 52 48 M 100 40 L 100 32 M 100 40 L 104 40 M 100 40 L 100 48 M 104 40 L 112 40 M 104 40 L 112 40 L 120 40 M 112 40 L 120 40 L 128 40 M 120 40 L 128 40 M 132 36 L 132 32 M 132 44 L 132 48 M 136 40 L 144 40 M 136 40 L 144 40 L 152 40 M 144 40 L 152 40 M 52 48 L 52 64 M 52 48 L 52 64 M 100 48 L 100 64 M 100 48 L 100 64 M 52 72 L 52 64 M 52 72 L 56 72 L 64 72 M 56 72 L 64 72 L 72 72 M 64 72 L 72 72 L 80 72 M 72 72 L 80 72 L 88 72 M 80 72 L 88 72 L 96 72 M 88 72 L 96 72 M 100 72 L 100 64 M 100 72 L 96 72 M 60 104 L 64 104 M 60 104 L 56 112 M 64 104 L 72 104 M 64 104 L 72 104 L 80 104 M 72 104 L 80 104 L 88 104 M 80 104 L 88 104 M 92 104 L 88 104 M 92 104 L 88 112 M 48 128 L 56 112 L 48 128 M 64 120 L 52 120 M 66 124 L 64 128 M 72 120 L 84 120 M 80 128 L 88 112 L 80 128 M 96 120 L 84 120 M 96 120 L 104 120 M 96 120 L 104 120 M 28 136 L 44 136 M 28 136 L 24 144 M 32 136 L 44 136 M 40 144 L 48 128 L 40 144 M 56 144 L 64 128 L 56 144 M 72 144 L 80 128 L 72 144 M 88 136 L 76 136 M 16 160 L 24 144 L 16 160 M 36 152 L 40 144 M 64 160 L 56 144 L 64 160 L 72 144 L 64 160 M 12 168 L 16 168 M 12 168 L 16 160 M 16 168 L 24 168 M 16 168 L 24 168 L 32 168 M 24 168 L 32 168 L 40 168 M 32 168 L 40 168 M 44 168 L 40 168 M 44 168 L 48 176 M 72 176 L 64 160 L 72 176 M 56 192 L 48 176 L 56 192 M 64 192 L 72 176 L 64 192 M 60 200 L 56 192 M 60 200 L 64 192&quot; fill=&quot;none&quot;/&gt;
&lt;path d=&quot;&quot; fill=&quot;none&quot; stroke-dasharray=&quot;3 3&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;132&quot; x2=&quot;132&quot; y1=&quot;16&quot; y2=&quot;4&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;32&quot; x2=&quot;44&quot; y1=&quot;40&quot; y2=&quot;40&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;32&quot; x2=&quot;44&quot; y1=&quot;40&quot; y2=&quot;40&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;40&quot; x2=&quot;44&quot; y1=&quot;40&quot; y2=&quot;40&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;152&quot; x2=&quot;164&quot; y1=&quot;40&quot; y2=&quot;40&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;152&quot; x2=&quot;164&quot; y1=&quot;40&quot; y2=&quot;40&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;160&quot; x2=&quot;164&quot; y1=&quot;40&quot; y2=&quot;40&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;132&quot; x2=&quot;132&quot; y1=&quot;48&quot; y2=&quot;76&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;132&quot; x2=&quot;132&quot; y1=&quot;48&quot; y2=&quot;76&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;132&quot; x2=&quot;132&quot; y1=&quot;64&quot; y2=&quot;76&quot;/&gt;
&lt;line marker-end=&quot;url(#triangle)&quot; x1=&quot;88&quot; x2=&quot;92&quot; y1=&quot;136&quot; y2=&quot;136&quot;/&gt;
&lt;circle class=&quot;open&quot; cx=&quot;132&quot; cy=&quot;40&quot; r=&quot;4&quot;/&gt;
&lt;circle class=&quot;open&quot; cx=&quot;132&quot; cy=&quot;40&quot; r=&quot;4&quot;/&gt;
&lt;circle class=&quot;open&quot; cx=&quot;132&quot; cy=&quot;40&quot; r=&quot;4&quot;/&gt;
&lt;circle class=&quot;open&quot; cx=&quot;132&quot; cy=&quot;40&quot; r=&quot;4&quot;/&gt;
&lt;circle class=&quot;open&quot; cx=&quot;68&quot; cy=&quot;120&quot; r=&quot;4&quot;/&gt;
&lt;circle class=&quot;open&quot; cx=&quot;68&quot; cy=&quot;120&quot; r=&quot;4&quot;/&gt;
&lt;circle class=&quot;open&quot; cx=&quot;68&quot; cy=&quot;120&quot; r=&quot;4&quot;/&gt;
&lt;circle class=&quot;solid&quot; cx=&quot;36&quot; cy=&quot;152&quot; r=&quot;4&quot;/&gt;
&lt;/svg&gt;
&lt;/div&gt;
&lt;h3 id=&quot;blockdiag&quot; name=&quot;blockdiag&quot;&gt;blockdiag&lt;/h3&gt;
&lt;p&gt;Block diagram&lt;/p&gt;
&lt;p&gt;blockdiag {
A -&amp;gt; B -&amp;gt; C -&amp;gt; D;
A -&amp;gt; E -&amp;gt; F -&amp;gt; G;
}&lt;/p&gt;
&lt;h2 id=&quot;mdx_include&quot; name=&quot;mdx_include&quot;&gt;mdx_include&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;{! nginx_mod_authrequest/auth1.py !}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;GFM+style+check+lists&quot; name=&quot;GFM+style+check+lists&quot;&gt;GFM style check lists&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; disabled &gt; foo&lt;/li&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; disabled checked&gt; bar&lt;/li&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; disabled &gt; baz&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;my+mdx+variables&quot; name=&quot;my+mdx+variables&quot;&gt;my mdx variables&lt;/h2&gt;
&lt;p&gt;We use &lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/snippets/2019/adhoc-rsync/send-nc&quot;&gt;snippets&lt;/a&gt; as an example.&lt;/p&gt;
&lt;p&gt;How we handle missing ${VARS}.&lt;/p&gt;</content>
</entry>
<entry>
<title>DVTM</title>
<link href="https://www.0ink.net/posts/2021/2021-12-23-dvtm.html"></link>
<id>urn:uuid:e6d24416-ee6b-7807-ab2b-cf02e9a1282a</id>
<updated>2022-03-03T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[The other day I found dvtm.  Looking at it, it
looks very nice.  It appeals to me because I am particularly
fond of text user interfaces.

At the end I choose not to use it because:

...]]></summary>
<content type="html">&lt;p&gt;The other day I found &lt;a href=&quot;http://www.brain-dump.org/projects/dvtm/&quot;&gt;dvtm&lt;/a&gt;.  Looking at it, it
looks very nice.  It appeals to me because I am particularly
fond of text user interfaces.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2021/dvtm-screencast.gif&quot; alt=&quot;screencast&quot; /&gt;&lt;/p&gt;
&lt;p&gt;At the end I choose not to use it because:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;terminal support was less than 100% useful.&lt;/li&gt;
&lt;li&gt;At the end of the day using the mouse is just more convenient.&lt;/li&gt;
&lt;li&gt;It is not as ubiquitous as for example &lt;a href=&quot;https://www.gnu.org/software/screen/&quot;&gt;screen&lt;/a&gt;.  So it
is easier to just use &lt;a href=&quot;https://www.gnu.org/software/screen/&quot;&gt;screen&lt;/a&gt; that can be set-up much
more easily.&lt;/li&gt;
&lt;li&gt;most of the time I am already on a window session, so there is
not that many opportunities to use this.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;dvtm+Cheat+Sheet&quot; name=&quot;dvtm+Cheat+Sheet&quot;&gt;dvtm Cheat Sheet&lt;/h2&gt;
&lt;p&gt;This is a simple cheat sheet for &lt;a href=&quot;http://www.brain-dump.org/projects/dvtm/&quot;&gt;dvtm&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This uses the default &lt;code&gt;mod&lt;/code&gt; key: &lt;code&gt;C-g&lt;/code&gt;.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key Seq&lt;/th&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;C-g M&lt;/td&gt;
&lt;td&gt;Toggle mouse mode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-g Enter&lt;/td&gt;
&lt;td&gt;Zoom current window to master area&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-g h&lt;/td&gt;
&lt;td&gt;Shrink master area&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-g l&lt;/td&gt;
&lt;td&gt;Enlarged master area&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-g Spc&lt;/td&gt;
&lt;td&gt;Toggle layout (vertical stack, bottom stack, grid, full screen)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-g f&lt;/td&gt;
&lt;td&gt;Vertical stack&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-g b&lt;/td&gt;
&lt;td&gt;Bottom stack&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-g g&lt;/td&gt;
&lt;td&gt;Grid layout&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-g m&lt;/td&gt;
&lt;td&gt;Full screen&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-g 0&lt;/td&gt;
&lt;td&gt;view all windows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-g c&lt;/td&gt;
&lt;td&gt;Create window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-g j&lt;/td&gt;
&lt;td&gt;Focus on next window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-g k&lt;/td&gt;
&lt;td&gt;Focus on previous window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-g m&lt;/td&gt;
&lt;td&gt;Minimize window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-g s&lt;/td&gt;
&lt;td&gt;Toggle status bar&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-g 1-9&lt;/td&gt;
&lt;td&gt;Focus on window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-g TAB&lt;/td&gt;
&lt;td&gt;Toggle focus (last window)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-g q&lt;/td&gt;
&lt;td&gt;Quit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-g C-l&lt;/td&gt;
&lt;td&gt;Redraw&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-g r&lt;/td&gt;
&lt;td&gt;Redraw&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-g PgUp&lt;/td&gt;
&lt;td&gt;Scroll back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-g PgDn&lt;/td&gt;
&lt;td&gt;Scroll Fwd&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-g C-g&lt;/td&gt;
&lt;td&gt;Send C-g&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key Seq&lt;/th&gt;
&lt;th&gt;Additional functions&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;C-g C&lt;/td&gt;
&lt;td&gt;Create window with current directory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-g J&lt;/td&gt;
&lt;td&gt;Focus on next window &amp;quot;m&amp;quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-g K&lt;/td&gt;
&lt;td&gt;Focus on prev window ?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-g i&lt;/td&gt;
&lt;td&gt;Increase # windows in master area&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-g d&lt;/td&gt;
&lt;td&gt;decrease # windows in master area&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-g s&lt;/td&gt;
&lt;td&gt;Toggle status bar position (top or bottom)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Other functions that I don&#039;t understand or haven&#039;t configured:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;tagging&lt;/li&gt;
&lt;li&gt;copymode&lt;/li&gt;
&lt;li&gt;status bar&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Migration to Pelican</title>
<link href="https://www.0ink.net/posts/2021/2021-12-22-pelican.html"></link>
<id>urn:uuid:ddabb242-951d-26ad-6621-0accbd3b15ad</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Finally got fedup with github pages and its jekyll
static site generator.  Essentially things would break without
any particular reason and there would be nearly no way to
tell what went wrong.  I addition, it was not easy to test
changes before making them public.
So I switched to pelican, essentially because it was
...]]></summary>
<content type="html">&lt;p&gt;Finally got fedup with &lt;a href=&quot;https://pages.github.com/&quot;&gt;github pages&lt;/a&gt; and its &lt;a href=&quot;https://jekyllrb.com/&quot;&gt;jekyll&lt;/a&gt;
static site generator.  Essentially things would break without
any particular reason and there would be nearly no way to
tell what went wrong.  I addition, it was not easy to test
changes before making them public.&lt;/p&gt;
&lt;p&gt;So I switched to &lt;a href=&quot;https://blog.getpelican.com/&quot;&gt;pelican&lt;/a&gt;, essentially because it was
a static site generator that is part of the &lt;a href=&quot;https://voidlinux.org/&quot;&gt;void&lt;/a&gt;
software repository.&lt;/p&gt;
&lt;p&gt;I don&#039;t really like it that much as its documentation is not
very good.  But eventually I got it to work.&lt;/p&gt;
&lt;p&gt;I was able to get one of their public templates to work and
tweaked to match my preferences.&lt;/p&gt;
&lt;p&gt;I also was able to add:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;automatic tag generation
&lt;ul&gt;
&lt;li&gt;this is done before processing input files.  i.e. a script
reads existing content and modifies files as needed with
automatic tags.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;sitemap generator
&lt;ul&gt;
&lt;li&gt;this is done as post-processing stage.  A script reads
generated html files and generates the sitemap accordingly.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The most useful feature I found is its that I can preview
changes before commiting them.&lt;/p&gt;</content>
</entry>
<entry>
<title>Storing secrets in git</title>
<link href="https://www.0ink.net/posts/2021/2021-12-22-git-crypt.html"></link>
<id>urn:uuid:c618eb91-bbcc-c9dd-ae0a-711aa71d5b7a</id>
<updated>2022-01-04T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[So I gave into the temptation to store &quot;secret&quot; data into
a git repository.  Of course, to keep things
safer, I chose to use an encryption tool.  So I tested:

git-crypt
git-secret
...]]></summary>
<content type="html">&lt;p&gt;So I gave into the temptation to store &amp;quot;secret&amp;quot; data into
a &lt;a href=&quot;https://git-scm.com/&quot;&gt;git&lt;/a&gt; repository.  Of course, to keep things
safer, I chose to use an encryption tool.  So I tested:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/AGWA/git-crypt&quot;&gt;git-crypt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://git-secret.io/&quot;&gt;git-secret&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;git-secret&quot; name=&quot;git-secret&quot;&gt;git-secret&lt;/h2&gt;
&lt;p&gt;So, I tested &lt;a href=&quot;https://git-secret.io/&quot;&gt;git-secret&lt;/a&gt;.  It seems to work but was in my
opinion cumbersome.&lt;/p&gt;
&lt;p&gt;Furthermore, the version I tested, which
was the one from the &lt;a href=&quot;https://voidlinux.org/&quot;&gt;void&lt;/a&gt; has a bug whereby adding
files for encryption would update the &lt;code&gt;.gitignore&lt;/code&gt; file
but would forget to put an EOL at the end of the file.&lt;/p&gt;
&lt;p&gt;The main issue I have is that you need to explicitly issue
the command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git-secret hide
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To hide files.  Alternatively you can include this in your
pre-commit hook, but that brings its own issues along.&lt;/p&gt;
&lt;p&gt;Overall it was not the best experience.&lt;/p&gt;
&lt;h2 id=&quot;git-crypt&quot; name=&quot;git-crypt&quot;&gt;git-crypt&lt;/h2&gt;
&lt;p&gt;At the end I opted for &lt;a href=&quot;https://github.com/AGWA/git-crypt&quot;&gt;git-crypt&lt;/a&gt;, which is more seamless
and requires less user interaction for it to work.&lt;/p&gt;
&lt;h3 id=&quot;git-crypt+mini+howto&quot; name=&quot;git-crypt+mini+howto&quot;&gt;git-crypt mini howto&lt;/h3&gt;
&lt;p&gt;I installed &lt;a href=&quot;https://github.com/AGWA/git-crypt&quot;&gt;git-crypt&lt;/a&gt; using my distro package installation
command.&lt;/p&gt;
&lt;p&gt;Initialize an existing git repo and export encryption key:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd repo
git-crypt init
git-crypt export /path/to/key
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The exported key now needs to be shared between all the repo
users.  For example can be saved into a secret variable in
the CI/CD pipeline system.&lt;/p&gt;
&lt;p&gt;Select the files that need to be protected by creating
a &lt;code&gt;.gitattributes&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;secretfile filter=git-crypt diff=git-crypt
*.key filter=git-crypt diff=git-crypt
secretdir/** filter=git-crypt diff=git-crypt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Like a &lt;code&gt;.gitignore&lt;/code&gt; file, it can match wildcards and should
be checked into the repository.  Make sure you don&#039;t accidentally
encrypt the &lt;code&gt;.gitattributes&lt;/code&gt; file itself (or other git files like
&lt;code&gt;.gitignore&lt;/code&gt; or &lt;code&gt;.gitmodules&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt; &lt;em&gt;Make sure your &lt;code&gt;.gitattributes&lt;/code&gt; rules are in place before
you add sensitive files, or those files won&#039;t be encrypted!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;After cloning a repository with encrypted files, unlock with
the secret key:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git-crypt unlock /path/to/key
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&#039;s all you need to do - after &lt;a href=&quot;https://github.com/AGWA/git-crypt&quot;&gt;git-crypt&lt;/a&gt; is set up (either
with &lt;code&gt;git-crypt init&lt;/code&gt; or &lt;code&gt;git-crypt unlock&lt;/code&gt;), you can use git
normally - encryption and decryption happen transparently.&lt;/p&gt;
&lt;h3 id=&quot;Verifying+that+git-crypt+is+working&quot; name=&quot;Verifying+that+git-crypt+is+working&quot;&gt;Verifying that git-crypt is working&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The simplest way:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;git crypt status&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;The native way:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;git check-attr -a -- &amp;lt;path&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Checking object hashes (these shouldn&#039;t match):
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;git hash-object &amp;lt;path&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cat &amp;lt;path&amp;gt; | git hash-object --stdin&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Enable syslog with void</title>
<link href="https://www.0ink.net/posts/2021/2021-12-21-void-syslog.html"></link>
<id>urn:uuid:8a08c4f3-b38a-e598-0376-6b3aa54dead5</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[In void Linux, the default is without logging.  Most
cases it is OK for desktop use.
If you want to enable syslog service in void,
you need to install:
socklog-void
Also to let your user have access to the logs, use:
...]]></summary>
<content type="html">&lt;p&gt;In &lt;a href=&quot;https://voidlinux.org&quot;&gt;void&lt;/a&gt; Linux, the default is without logging.  Most
cases it is OK for desktop use.&lt;/p&gt;
&lt;p&gt;If you want to enable &lt;a href=&quot;https://en.wikipedia.org/wiki/Syslog&quot;&gt;syslog&lt;/a&gt; service in &lt;a href=&quot;https://voidlinux.org&quot;&gt;void&lt;/a&gt;,
you need to install:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;socklog-void&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also to let your user have access to the logs, use:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;usermod -aG socklog &amp;lt;your-username&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because I like to have just a single directory for everything and use
&lt;code&gt;grep&lt;/code&gt;, I do the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rm -rf /var/log/socklog/?*
mkdir /var/log/socklog/everything
ln -s socklog/everything/current /var/log/messages.log&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create the file &lt;code&gt;/var/log/socklog/everything/config&lt;/code&gt; with these
contents:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;+*
u&amp;lt;syslog-server-ip&amp;gt;:514&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Enable daemons...&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ln -s /etc/sv/socklog-unix /var/service/
ln -s /etc/sv/nanoklogd /var/service/&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reload &lt;code&gt;svlogd&lt;/code&gt; (if it was already running)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;killall -1 svlogd&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Reference%3A&quot; name=&quot;Reference%3A&quot;&gt;Reference:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.voidlinux.org/config/services/logging.html&quot;&gt;voidlinux logging&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Stupid SSL tricks</title>
<link href="https://www.0ink.net/posts/2021/2021-12-21-ssl-tips.html"></link>
<id>urn:uuid:cb8dd9df-6d7a-7c36-0721-a34e5dfa5278</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Some hints and tips foor doing SSL related things:
Netcat for SSL
This command lets you connect to a SSL server (a-la netcat):
cat request.txt | openssl s_client -connect server:443
Creating self-signed certificates
This is a single command to generate a self-signed certificate:
...]]></summary>
<content type="html">&lt;p&gt;Some hints and tips foor doing SSL related things:&lt;/p&gt;
&lt;h2 id=&quot;Netcat+for+SSL&quot; name=&quot;Netcat+for+SSL&quot;&gt;Netcat for SSL&lt;/h2&gt;
&lt;p&gt;This command lets you connect to a SSL server (a-la netcat):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat request.txt | openssl s_client -connect server:443&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Creating+self-signed+certificates&quot; name=&quot;Creating+self-signed+certificates&quot;&gt;Creating self-signed certificates&lt;/h2&gt;
&lt;p&gt;This is a single command to generate a self-signed certificate:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;openssl req -new \
      -newkey rsa:4096 \
      -days 365 \
      -nodes -x509 \
      -subj &quot;/C=NL/ST=ZH/L=Den Haag/O=HomeBase/CN=$fqdn&quot; \
      -keyout $ca_root/$fqdn/$fqdn.key \
      -out $ca_root/$fqdn/$fqdn.cer&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is unlike other recipes where you create a &lt;code&gt;csr&lt;/code&gt; and &lt;code&gt;key&lt;/code&gt;
first and then create the &lt;code&gt;certificate&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;Checking+and+verifying+certificates&quot; name=&quot;Checking+and+verifying+certificates&quot;&gt;Checking and verifying certificates&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Check certificate
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;openssl x509 -in server.crt -text -noout&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Check SSL key and verify consistency
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;openssl rsa -in server.key -check&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Check CSR and print CSR data
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;openssl req -text -noout -verify -in server.csr&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Verify that certificate and key matches:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;openssl x509 -noout -modulus -in server.crt| openssl md5&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;openssl rsa -noout -modulus -in server.key| openssl md5&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Check SSL Certificate expiration date
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;openssl x509 -dates -noout -in hydssl.cer&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Check+SSL+connection&quot; name=&quot;Check+SSL+connection&quot;&gt;Check SSL connection&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Tests connectivity to an HTTPS service:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;openssl s_client -connect &amp;lt;hostname&amp;gt;:443&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Prints all certificates in the certificate chain presented by the
SSL service. Useful when troubleshooting missing intermediate CA
certificate issues.
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;openssl s_client -connect &amp;lt;hostname&amp;gt;:&amp;lt;port&amp;gt; -showcerts&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Forces TLSv1 and DTLSv1.
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;openssl s_client -connect &amp;lt;hostname&amp;gt;:&amp;lt;port&amp;gt; -tls1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;openssl s_client -connect &amp;lt;hostname&amp;gt;:&amp;lt;port&amp;gt; -dtls1&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Forces a specific cipher. This option is useful in testing enabled
SSL ciphers. Use the &lt;code&gt;openssl ciphers&lt;/code&gt; command to see a list of
available ciphers for OpenSSL.
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;openssl s_client -connect &amp;lt;hostname&amp;gt;:&amp;lt;port&amp;gt; -cipher DHE-RSA-AES256-SHA&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For troubleshooting connection and SSL handshake problems, see the
following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If there is a connection problem reaching the domain, the OpenSSL
&lt;code&gt;s_client -connect&lt;/code&gt; command waits until a timeout occurs and prints
an error, such as &lt;code&gt;connect: Operation timed out&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If you use the OpenSSL client to connect to a non-SSL service, the
client connects but the SSL handshake doesn&#039;t happen. &lt;code&gt;CONNECTED (00000003)&lt;/code&gt; prints as soon as a socket
opens, but the client waits until a timeout occurs and prints an
error message, such as &lt;code&gt;44356:error:140790E5:SSL routines:SSL23_WRITE:ssl handshake failure:/SourceCache/OpenSSL098/OpenSSL098-47.1/src/ssl/s23_lib.c:182:.&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After disabling a weak cipher, you can verify if it has been disabled
or not with the following command.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;openssl s_client -connect google.com:443 -cipher EXP-RC4-MD5&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Check+SSL+certificates+on+a+remote+server%3A&quot; name=&quot;Check+SSL+certificates+on+a+remote+server%3A&quot;&gt;Check SSL certificates on a remote server:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Check who has issued the SSL certificate:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;echo | openssl s_client -servername howtouselinux.com -connect howtouselinux.com:443 2&amp;gt;/dev/null | openssl x509 -noout -issuer&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Check whom the SSL certificate is issued to:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;echo | openssl s_client -servername howtouselinux.com -connect howtouselinux.com:443 2&amp;gt;/dev/null | openssl x509 -noout -subject&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Check for what dates the SSL certificate is valid:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;echo | openssl s_client -servername howtouselinux.com -connect howtouselinux.com:443 2&amp;gt;/dev/null | openssl x509 -noout -dates&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Show the SHA1 fingerprint of the SSL certificate:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;echo | openssl s_client -servername www.howtouselinux.com -connect www.howtouselinux.com:443 2&amp;gt;/dev/null | openssl x509 -noout -fingerprint&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Extract all information from the SSL certificate (decoded)
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;echo | openssl s_client -servername www.howtouselinux.com -connect www.howtouselinux.com:443 2&amp;gt;/dev/null | openssl x509 -noout -text&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Show the SSL certificate itself (encoded):
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;echo | openssl s_client -servername howtouselinux.com -connect howtouselinux.com:443 2&amp;gt;/dev/null | openssl x509&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Becoming+your+own+CA&quot; name=&quot;Becoming+your+own+CA&quot;&gt;Becoming your own &lt;code&gt;CA&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;The easiest way is to use &lt;a href=&quot;https://github.com/FiloSottile/mkcert&quot;&gt;mkcert&lt;/a&gt;.  &lt;a href=&quot;https://github.com/FiloSottile/mkcert&quot;&gt;mkcert&lt;/a&gt; is a
command line tool that automates most of the activities related
a CA.&lt;/p&gt;
&lt;p&gt;Otherwise, &lt;a href=&quot;https://deliciousbrains.com/ssl-certificate-authority-for-local-https-development/&quot;&gt;this article&lt;/a&gt; by Brad Touesnard explains
the process fully.&lt;/p&gt;</content>
</entry>
<entry>
<title>Linux Post Install tasks</title>
<link href="https://www.0ink.net/posts/2021/2021-12-21-linux-tips.html"></link>
<id>urn:uuid:ea9fa619-7796-4a96-baf4-e3e5f6e6fa11</id>
<updated>2021-12-26T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[These tips are for void linux as that is the distro
I am using nowadays.

mate tricks: change background from cli

dconf write /org/mate/desktop/background/picture-filename "'PATH-TO-JPEG'"
...]]></summary>
<content type="html">&lt;p&gt;These tips are for &lt;a href=&quot;https://voidlinux.org&quot; title=&quot;Void Linux&quot;&gt;void linux&lt;/a&gt; as that is the distro
I am using nowadays.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;mate tricks: change background from cli
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;dconf write /org/mate/desktop/background/picture-filename &quot;&#039;PATH-TO-JPEG&#039;&quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;web page to check if your browser is html5 compliant:
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/html5&quot;&gt;https://www.youtube.com/html5&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;HotKeys&quot; name=&quot;HotKeys&quot;&gt;HotKeys&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Install &lt;code&gt;xbindkeys&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Add to startup? &lt;code&gt;$HOME/.xprofile&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Create default config with &lt;code&gt;xbindkeys -d &amp;gt; $HOME/.xbindkeysrc&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Lookup key combinations:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;xbindkeys --multikey&lt;/code&gt; or&lt;/li&gt;
&lt;li&gt;&lt;code&gt;xbindkeys --key&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;update bindings &lt;code&gt;xbindkeys --poll-rc&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rofi&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;A good addition to this is &lt;code&gt;rofi&lt;/code&gt;.  Which is a dynamic menu.&lt;/li&gt;
&lt;li&gt;Create a xbindkey shortcut to run rofi with:&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rofi -show-icons  -modi drun,window  -show drun&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Additional+software&quot; name=&quot;Additional+software&quot;&gt;Additional software&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Additional software (Tested on void linux):
&lt;ul&gt;
&lt;li&gt;Tip: use &lt;code&gt;sed -e &#039;s/#.*$//&#039;&lt;/code&gt; to strip comments!&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;{! void-installation/swlist-extras.txt !}
&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>My tale of IPv6 blues</title>
<link href="https://www.0ink.net/posts/2021/2021-12-21-ipv6-blues.html"></link>
<id>urn:uuid:4acd7759-c135-262f-7560-c89cb4f18ede</id>
<updated>2024-02-02T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[My ISP provider is KPN.  They recently enabled
IPv6 in my street.  I was using before a IPv6 Tunnel Broker,
starting with SixXS and after they went out,
with Hurricane Electric.  So naturally,
I decided to switch to KPN's native IPv6 service.
They provide a /64 prefix, which is reasonable.  Would be better
...]]></summary>
<content type="html">&lt;p&gt;My ISP provider is &lt;a href=&quot;https://www.kpn.com&quot; title=&quot;KPN&quot;&gt;KPN&lt;/a&gt;.  They recently enabled
IPv6 in my street.  I was using before a IPv6 Tunnel Broker,
starting with &lt;a href=&quot;https://www.sixxs.net&quot; title=&quot;SixXS&quot;&gt;SixXS&lt;/a&gt; and after they went out,
with &lt;a href=&quot;https://tunnelbroker.net&quot; title=&quot;Hurricane Electric&quot;&gt;Hurricane Electric&lt;/a&gt;.  So naturally,
I decided to switch to KPN&#039;s native IPv6 service.&lt;/p&gt;
&lt;p&gt;They provide a /64 prefix, which is reasonable.  Would be better
if they provided a /48, but /64 is better than other providers.&lt;/p&gt;
&lt;p&gt;So to start using KPN as the IPv6 turned out very easy.  Their default
configuration works right out of the box if you have single flat
network.&lt;/p&gt;
&lt;p&gt;I used to have a router/FW between the KPN modem and my network,
but at some point I decided to go for a flat network design.  With
this, (without having to do anything) once KPN enabled IPv6, all my
equipment that was IPv6 capable started using IPv6.  It was like
magic.&lt;/p&gt;
&lt;p&gt;I run a number of server systems in my home network, using
&lt;a href=&quot;https://alpinelinux.org&quot; title=&quot;Alpine Linux&quot;&gt;Alpine Linux&lt;/a&gt; as its operating system.&lt;/p&gt;
&lt;p&gt;For some reason, these servers would be able to use IPv6 at first
(either via static configuration or auto-configuration), but stop
working after a few minutes (often after 65 seconds).&lt;/p&gt;
&lt;p&gt;Things worked fine for my &lt;a href=&quot;https://voidlinux.org/&quot;&gt;void-linux&lt;/a&gt; systems.  These
use &lt;a href=&quot;https://en.wikipedia.org/wiki/NetworkManager&quot;&gt;NetworkManager&lt;/a&gt; so I guess this helps.&lt;/p&gt;
&lt;p&gt;Even googling around I was not able to find a solution.  Apparently
doing this would re-enable things:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ip -6 a del $ip6_addr dev $IFACE
ip -6 a add $ip6_addr dev $IFACE&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So what I did is, I wrote a small little script that would run
every 45 seconds, and do this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ip -6 address save dev $IFACE scope global &amp;gt; $savefile
ip -6 address flush dev $IFACE scope global
ip -6 address restore &amp;lt; $savefile 2&amp;gt;&amp;amp;1 | grep -v &#039;RTNETLINK answers: File exists&#039; || :&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Again, I have no idea what is going on.&lt;/p&gt;
&lt;p&gt;Eventually I changed my set-up to have something like
this:&lt;/p&gt;
&lt;div&gt;&lt;svg class=&quot;bob&quot; font-family=&quot;arial&quot; font-size=&quot;14&quot; height=&quot;48&quot; width=&quot;312&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;defs&gt;
&lt;marker id=&quot;triangle&quot; markerHeight=&quot;10&quot; markerUnits=&quot;strokeWidth&quot; markerWidth=&quot;10&quot; orient=&quot;auto&quot; refX=&quot;15&quot; refY=&quot;10&quot; viewBox=&quot;0 0 50 20&quot;&gt;
&lt;path d=&quot;M 0 0 L 30 10 L 0 20 z&quot;/&gt;
&lt;/marker&gt;
&lt;/defs&gt;
&lt;style&gt;

    line, path {
      stroke: black;
      stroke-width: 2;
      stroke-opacity: 1;
      fill-opacity: 1;
      stroke-linecap: round;
      stroke-linejoin: miter;
    }
    circle {
      stroke: black;
      stroke-width: 2;
      stroke-opacity: 1;
      fill-opacity: 1;
      stroke-linecap: round;
      stroke-linejoin: miter;
    }
    circle.solid {
      fill:black;
    }
    circle.open {
      fill:transparent;
    }
    tspan.head{
        fill: none;
        stroke: none;
    }
    
&lt;/style&gt;
&lt;path d=&quot; M 4 8 L 8 8 M 4 8 L 4 16 M 8 8 L 16 8 M 8 8 L 16 8 L 24 8 M 16 8 L 24 8 L 32 8 M 24 8 L 32 8 L 40 8 M 32 8 L 40 8 L 48 8 M 40 8 L 48 8 L 56 8 M 48 8 L 56 8 L 64 8 M 56 8 L 64 8 L 72 8 M 64 8 L 72 8 L 80 8 M 72 8 L 80 8 M 84 8 L 80 8 M 84 8 L 84 16 M 116 8 L 120 8 M 116 8 L 116 16 M 120 8 L 128 8 M 120 8 L 128 8 L 136 8 M 128 8 L 136 8 L 144 8 M 136 8 L 144 8 L 152 8 M 144 8 L 152 8 L 160 8 M 152 8 L 160 8 L 168 8 M 160 8 L 168 8 M 172 8 L 168 8 M 172 8 L 172 16 M 204 8 L 208 8 M 204 8 L 204 16 M 208 8 L 216 8 M 208 8 L 216 8 L 224 8 M 216 8 L 224 8 L 232 8 M 224 8 L 232 8 L 240 8 M 232 8 L 240 8 L 248 8 M 240 8 L 248 8 L 256 8 M 248 8 L 256 8 L 264 8 M 256 8 L 264 8 L 272 8 M 264 8 L 272 8 L 280 8 M 272 8 L 280 8 L 288 8 M 280 8 L 288 8 L 296 8 M 288 8 L 296 8 L 304 8 M 296 8 L 304 8 M 308 8 L 304 8 M 308 8 L 308 16 M 4 16 L 4 32 M 4 16 L 4 32 M 84 16 L 84 32 M 84 16 L 84 32 M 96 24 L 84 24 M 96 24 L 116 24 M 96 24 L 116 24 M 104 24 L 116 24 M 116 16 L 116 32 M 116 16 L 116 32 M 172 16 L 172 32 M 172 16 L 172 32 M 184 24 L 172 24 M 184 24 L 204 24 M 184 24 L 204 24 M 192 24 L 204 24 M 204 16 L 204 32 M 204 16 L 204 32 M 308 16 L 308 32 M 308 16 L 308 32 M 4 40 L 4 32 M 4 40 L 8 40 L 16 40 M 8 40 L 16 40 L 24 40 M 16 40 L 24 40 L 32 40 M 24 40 L 32 40 L 40 40 M 32 40 L 40 40 L 48 40 M 40 40 L 48 40 L 56 40 M 48 40 L 56 40 L 64 40 M 56 40 L 64 40 L 72 40 M 64 40 L 72 40 L 80 40 M 72 40 L 80 40 M 84 40 L 84 32 M 84 40 L 80 40 M 116 40 L 116 32 M 116 40 L 120 40 L 128 40 M 120 40 L 128 40 L 136 40 M 128 40 L 136 40 L 144 40 M 136 40 L 144 40 L 152 40 M 144 40 L 152 40 L 160 40 M 152 40 L 160 40 L 168 40 M 160 40 L 168 40 M 172 40 L 172 32 M 172 40 L 168 40 M 204 40 L 204 32 M 204 40 L 208 40 L 216 40 M 208 40 L 216 40 L 224 40 M 216 40 L 224 40 L 232 40 M 224 40 L 232 40 L 240 40 M 232 40 L 240 40 L 248 40 M 240 40 L 248 40 L 256 40 M 248 40 L 256 40 L 264 40 M 256 40 L 264 40 L 272 40 M 264 40 L 272 40 L 280 40 M 272 40 L 280 40 L 288 40 M 280 40 L 288 40 L 296 40 M 288 40 L 296 40 L 304 40 M 296 40 L 304 40 M 308 40 L 308 32 M 308 40 L 304 40&quot; fill=&quot;none&quot;/&gt;
&lt;path d=&quot;&quot; fill=&quot;none&quot; stroke-dasharray=&quot;3 3&quot;/&gt;
&lt;text x=&quot;9&quot; y=&quot;28&quot;&gt;
KPN
&lt;/text&gt;
&lt;text x=&quot;41&quot; y=&quot;28&quot;&gt;
modem
&lt;/text&gt;
&lt;text x=&quot;121&quot; y=&quot;28&quot;&gt;
router
&lt;/text&gt;
&lt;text x=&quot;209&quot; y=&quot;28&quot;&gt;
HOME
&lt;/text&gt;
&lt;text x=&quot;249&quot; y=&quot;28&quot;&gt;
NETWORK
&lt;/text&gt;
&lt;/svg&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;router&lt;/code&gt; in between does Network Address Translation
and Firewalling.  The reasons I chose this is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;More &lt;em&gt;natural&lt;/em&gt; way of handling incoming connections&lt;/li&gt;
&lt;li&gt;Makes it possible to switch ISP&#039;s easier, down the line.
Alternatively, would make it possible to load-balance between
two ISPs.&lt;/li&gt;
&lt;li&gt;Can use &lt;code&gt;iptables&lt;/code&gt; for firewally.  I recognize that this is
only good for a geek like me though.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This causes problems with my IPv6 set-up because
now I have two segments.&lt;/p&gt;
&lt;p&gt;The KPN modem, assumes a flat network (with /64).  Since
I can&#039;t create routes in the KPN modem then the only
option would have been to NAT.  However the general
concensus is &lt;strong&gt;NOT&lt;/strong&gt; to NAT IPv6.  See
&lt;a href=&quot;https://blogs.infoblox.com/ipv6-coe/ipv6-nat-you-can-get-it-but-you-may-not-need-or-want-it/&quot;&gt;this article&lt;/a&gt;
for example.&lt;/p&gt;
&lt;p&gt;An alternative would have been to split the /64 into /80
segments.  Unfortunately, that doesn&#039;t work as a lot of the
software out there assumes that the network part of the IPv6
address is at most 64 bits.&lt;/p&gt;
&lt;p&gt;Linux has a feature built-in to the kernel called &lt;code&gt;proxy_ndp&lt;/code&gt;.
For &lt;a href=&quot;https://vtluug.org/wiki/Proxy_NDP&quot;&gt;example&lt;/a&gt;,&lt;/p&gt;
&lt;p&gt;The problem is that this does not scale well as the proxy address
needs to be statically configured.&lt;/p&gt;
&lt;p&gt;There are daemons that claim to proxy NDP for ranges:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/DanielAdolfsson/ndppd&quot;&gt;ndppd&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/setaou/ndp-proxy&quot;&gt;ndp-proxy&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These however did not work for me.&lt;/p&gt;
&lt;p&gt;So I wrote my own script to manage kernel &lt;code&gt;proxy_ndp&lt;/code&gt; entries
myself.  Essentially it does the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;listen on &lt;code&gt;ip monitor&lt;/code&gt; for IPv6 neighbor messages&lt;/li&gt;
&lt;li&gt;add and remove kernel data&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The whole script can be found &lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/snippets/2021/ipv6-whoes/ndpbr.sh&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Another approach is to use &lt;a href=&quot;https://thekelleys.org.uk/dnsmasq/doc.html&quot;&gt;dnsmasq&lt;/a&gt; and can
be found &lt;a href=&quot;https://quantum2.xyz/2019/03/08/ndp-proxy-route-ipv6-vpn-addresses/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;</content>
</entry>
<entry>
<title>Replacing emacs...</title>
<link href="https://www.0ink.net/posts/2021/2021-12-20-geany.html"></link>
<id>urn:uuid:e993b178-35eb-a7c4-ecfb-7655e9d45f73</id>
<updated>2021-12-26T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[So after over 30 years of using GNU emacs I have switched
to a more modern options.
So I am using:

Geany: For use in a Window environment (both X11 and MSWIN)
micro: For command line use
...]]></summary>
<content type="html">&lt;p&gt;So after over 30 years of using &lt;a href=&quot;https://www.gnu.org/software/emacs/&quot;&gt;GNU emacs&lt;/a&gt; I have switched
to a more modern options.&lt;/p&gt;
&lt;p&gt;So I am using:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.geany.org/&quot;&gt;Geany&lt;/a&gt;: For use in a Window environment (both X11 and MSWIN)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://micro-editor.github.io/&quot;&gt;micro&lt;/a&gt;: For command line use&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikibooks.org/wiki/Learning_the_vi_Editor/BusyBox_vi&quot;&gt;vi&lt;/a&gt;: For small environments&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Geany&quot; name=&quot;Geany&quot;&gt;Geany&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.geany.org/&quot;&gt;Geany&lt;/a&gt; is a nice programer&#039;s text editor.  I like that it has syntax
highlighting and has a modern UI.  It runs on Windows and Linux.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.geany.org/&quot;&gt;Geany&lt;/a&gt; has most of the features that I would like without much
need of customization.  Most of the customization that I have
done is around the area of getting indentation right and the
use of &lt;code&gt;spaces&lt;/code&gt; versus &lt;code&gt;tab&lt;/code&gt; when indenting.  Specially when
writing &lt;a href=&quot;https://www.python.org/&quot;&gt;python&lt;/a&gt; or &lt;a href=&quot;https://yaml.org/&quot;&gt;yaml&lt;/a&gt; files where the indentation is
important.&lt;/p&gt;
&lt;p&gt;A couple of featues that I thought were important but I am not
using much are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;macro recording&lt;/li&gt;
&lt;li&gt;split screens&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These features are available in geany as plugins, but I am not
using them.&lt;/p&gt;
&lt;h2 id=&quot;micro&quot; name=&quot;micro&quot;&gt;micro&lt;/h2&gt;
&lt;p&gt;A &lt;a href=&quot;https://micro-editor.github.io/&quot;&gt;micro&lt;/a&gt; is a modern, intuitive terminal based text editor.  It
follows modern key bindings and is fairly customizable.&lt;/p&gt;
&lt;p&gt;Actually it works well straight out of the box, but I did some
customizations in order to look similar to &lt;a href=&quot;https://www.geany.org/&quot;&gt;geany&lt;/a&gt;, and
so that it would work well with &lt;a href=&quot;https://www.putty.org/&quot;&gt;putty&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This is my &lt;a href=&quot;https://github.com/alejandroliu/dotfiles/blob/rcm-style/config/micro/bindings.json&quot;&gt;bindings&lt;/a&gt;.json file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{! https://github.com/alejandroliu/dotfiles/raw/rcm-style/config/micro/bindings.json !}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Links+about+micro&quot; name=&quot;Links+about+micro&quot;&gt;Links about micro&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/zyedidia/micro/blob/master/runtime/help/defaultkeys.md&quot;&gt;help default keys&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/zyedidia/micro/blob/master/runtime/help/options.md&quot;&gt;help config options&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/zyedidia/micro/blob/master/runtime/help/keybindings.md&quot;&gt;hekp key bindings&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Linux HDMI hotplug</title>
<link href="https://www.0ink.net/posts/2021/2021-02-28-hotplug-hdmi.html"></link>
<id>urn:uuid:ffa460c3-ba56-9c0f-dcb3-9c5200d0ee62</id>
<updated>2024-07-12T00:00:00+02:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[Now in 2024 this is usually no longer needed.
A more current solution is:

https://github.com/phillipberndt/autorandr

The point of this article is to document I workaround that I came
...]]></summary>
<content type="html">&lt;p&gt;Now in &lt;em&gt;2024&lt;/em&gt; this is usually no longer needed.&lt;/p&gt;
&lt;p&gt;A more current solution is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/phillipberndt/autorandr&quot;&gt;https://github.com/phillipberndt/autorandr&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The point of this article is to document I workaround that I came
up with to handle a HDMI KVM switch.&lt;/p&gt;
&lt;p&gt;What happens is that if my Linux PC is turned on while the KVM switch
is selecting the other PC, it fails to initialize the display, so
when you switch back to the Linux PC, no display is shown.&lt;/p&gt;
&lt;p&gt;The trick for this to work is to the use of &lt;a href=&quot;https://wiki.debian.org/udev&quot;&gt;udev&lt;/a&gt; and &lt;a href=&quot;https://xorg-team.pages.debian.net/xorg/howto/use-xrandr.html&quot;&gt;xrandr&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We use &lt;a href=&quot;https://wiki.debian.org/udev&quot;&gt;udev&lt;/a&gt; to detect the monitor being plugged in, and we use
&lt;a href=&quot;https://xorg-team.pages.debian.net/xorg/howto/use-xrandr.html&quot;&gt;xrandr&lt;/a&gt; to tell X windows to update the display.&lt;/p&gt;
&lt;p&gt;I don&#039;t think this happens very often, as the Linux defaults will
handle things properly most of the time.  In my case there were
a number of configurations where this makes sense:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Using the old &lt;a href=&quot;https://en.wikipedia.org/wiki/XDM_(display_manager)&quot;&gt;X display manager&lt;/a&gt; (XDM).  Which doesn&#039;t
seem to care for display change events.&lt;br /&gt;Personally, I like using XDM because is very minimalistic.&lt;/li&gt;
&lt;li&gt;Using a 4K HDMI EDID editor but connecting a monitor that does
not support 4K.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Figuring+out+udev&quot; name=&quot;Figuring+out+udev&quot;&gt;Figuring out udev&lt;/h2&gt;
&lt;p&gt;First in the agenda is to figure out what kind of event we should
be looking at.  For that, we use the command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;udevadm monitor&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;udevadm monitor --property&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With that we can determine what kind of &lt;a href=&quot;https://wiki.debian.org/udev&quot;&gt;udev&lt;/a&gt; events to look
for (if any).&lt;/p&gt;
&lt;p&gt;Next we need to figure out what keys we need to match.  Unfortunately
there is some guess work required as you need to figure out the &lt;code&gt;/dev&lt;/code&gt;
device path, whereas &lt;code&gt;udevadm monitor&lt;/code&gt; shows a &lt;code&gt;/devices/&lt;/code&gt; path.&lt;/p&gt;
&lt;p&gt;However, you manage, you need to use the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;udevadm info --query=all --name=/dev/dri/card0 --attribute-walk&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will show possible attributes in the &lt;a href=&quot;https://wiki.debian.org/udev&quot;&gt;udev&lt;/a&gt; rules key
format.&lt;/p&gt;
&lt;p&gt;Once we know the keys to use, we can know create the rules files.&lt;/p&gt;
&lt;p&gt;Rules are located in two locations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/usr/lib/udev/rules.d/&lt;/code&gt; : for system default rules&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/etc/udev/rules.d/&lt;/code&gt; : for local specific rules&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Essentially, we are waiting for the monitor configuration to change
and when that happens we will run a script. This is accomplish with
the following rules file (99-xwin-hotplug.rules):&lt;/p&gt;
&lt;script src=&quot;https://tortugalabs.github.io/embed-like-gist/embed.js?style=github&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showCopy=on&amp;amp;fetchFromJsDelivr=on&amp;amp;target=https://github.com/alejandroliu/0ink.net/blob/main/snippets/2021/xwin-hotplug/99-xwin-hotplug.rules&quot;&gt;&lt;/script&gt;
&lt;h2 id=&quot;Running+xrandr&quot; name=&quot;Running+xrandr&quot;&gt;Running xrandr&lt;/h2&gt;
&lt;p&gt;The script that is kicked off by &lt;a href=&quot;https://wiki.debian.org/udev&quot;&gt;udev&lt;/a&gt; is called &lt;code&gt;xwin-houtplug&lt;/code&gt;
and does the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Check if &lt;code&gt;DISPLAY&lt;/code&gt; is set. (i.e. running from Xorg session)&lt;/li&gt;
&lt;li&gt;Check if &lt;code&gt;Xorg&lt;/code&gt; is running.&lt;/li&gt;
&lt;li&gt;Determine the &lt;code&gt;DISPLAY&lt;/code&gt; and a suitable &lt;code&gt;XAUTHORITY&lt;/code&gt; file.&lt;/li&gt;
&lt;li&gt;Run &lt;a href=&quot;https://xorg-team.pages.debian.net/xorg/howto/use-xrandr.html&quot;&gt;xrandr&lt;/a&gt; to find connected monitors and relevant display modes.&lt;/li&gt;
&lt;li&gt;Check if the user has configured a &lt;em&gt;preferred&lt;/em&gt; video mode.&lt;/li&gt;
&lt;li&gt;Use the &lt;em&gt;preferred&lt;/em&gt; video mode or auto-detect:
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Preferred&lt;/em&gt;: &lt;code&gt;xrandr --output $monitor --mode $mode&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;auto-detect: &lt;code&gt;xrandr --output $monitor --auto&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;xrefresh&lt;/code&gt; for good measure.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;See script:&lt;/p&gt;
&lt;script src=&quot;https://tortugalabs.github.io/embed-like-gist/embed.js?style=github&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showCopy=on&amp;amp;fetchFromJsDelivr=on&amp;amp;target=https://github.com/alejandroliu/0ink.net/blob/main/snippets/2021/xwin-hotplug/xwin-hotplug&quot;&gt;&lt;/script&gt;
&lt;p&gt;To configure the &lt;em&gt;preferred&lt;/em&gt; video mode, create a file
&lt;code&gt;/etc/X11/vmode.prefs&lt;/code&gt; with the format:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;output:mode&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;HDMI-A-0:1920x1080
HDMI-A-1:2560x1440
DisplayPort-0:1280x720&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Refer to the output of &lt;a href=&quot;https://xorg-team.pages.debian.net/xorg/howto/use-xrandr.html&quot;&gt;xrandr&lt;/a&gt; for the connection names.&lt;/p&gt;
&lt;h2 id=&quot;monitor+warmplug&quot; name=&quot;monitor+warmplug&quot;&gt;monitor warmplug&lt;/h2&gt;
&lt;p&gt;Run the &lt;code&gt;xwin-hotplug&lt;/code&gt; script when Xorg starts.  This will make sure
that the selected &lt;em&gt;preferred&lt;/em&gt; video mode is used when the X session
starts.&lt;/p&gt;
&lt;h2 id=&quot;Configuring+Kernel+video+modes&quot; name=&quot;Configuring+Kernel+video+modes&quot;&gt;Configuring Kernel video modes&lt;/h2&gt;
&lt;p&gt;You can select a linux console &lt;em&gt;preferred&lt;/em&gt; video mode by adding the
&lt;code&gt;video&lt;/code&gt; option to the command line.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;video=HDMI-A-0:1280x720&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: The output specification here follows Linux kernel conventions
which are different from &lt;a href=&quot;https://xorg-team.pages.debian.net/xorg/howto/use-xrandr.html&quot;&gt;xrandr&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To get the name and current status of connectors, you can use the
following shell oneliner:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ for p in /sys/class/drm/*/status; do con=${p%/status}; echo -n &quot;${con#*/card?-}: &quot;; cat $p; done

DVI-I-1: connected
HDMI-A-1: disconnected
VGA-1: disconnected&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;See+Also&quot; name=&quot;See+Also&quot;&gt;See Also&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.thegeekdiary.com/beginners-guide-to-udev-in-linux/&quot;&gt;Beginners guide to udev&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://linuxconfig.org/tutorial-on-how-to-write-basic-udev-rules-in-linux&quot;&gt;Tutorial on how to write basic udev rules&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://opensource.com/article/18/11/udev&quot;&gt;Intro to udev&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wiki.archlinux.org/title/Kernel_mode_setting&quot;&gt;Kernel mode setting&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Alpine Boot switcher</title>
<link href="https://www.0ink.net/posts/2020/2020-10-04-alpine-boot-switcher.html"></link>
<id>urn:uuid:9bfed770-fd0c-9186-69d0-6533b60cedff</id>
<updated>2024-02-02T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[I boot from a USB boot drive using UEFI.  Because of the UEFI boot,
it just a matter of copying the files from the alpine
ISO to a USB thumbdrive VFAT32 partition.  Partition may be set to
EFI (but this doesn't seem to be required).
Since I would like to switch between different alpine versions,
I wrote a script to let me have multiple alpine versions and
...]]></summary>
<content type="html">&lt;p&gt;I boot from a USB boot drive using UEFI.  Because of the UEFI boot,
it just a matter of copying the files from the &lt;a href=&quot;https://alpinelinux.org/&quot;&gt;alpine&lt;/a&gt;
ISO to a USB thumbdrive VFAT32 partition.  Partition may be set to
EFI (but this doesn&#039;t seem to be required).&lt;/p&gt;
&lt;p&gt;Since I would like to switch between different &lt;a href=&quot;https://alpinelinux.org/&quot;&gt;alpine&lt;/a&gt; versions,
I wrote a script to let me have multiple &lt;a href=&quot;https://alpinelinux.org/&quot;&gt;alpine&lt;/a&gt; versions and
switch between them.  The boot partition can be kept &lt;code&gt;ro&lt;/code&gt; as the script
will automatically remount &lt;code&gt;rw&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In your boot/EFI partition, you need to have these two scripts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/snippets/2020/alpine-boot-switcher/select.sh&quot;&gt;select.sh&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/snippets/2020/alpine-boot-switcher/fixup.sh&quot;&gt;fixup.sh&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;code&gt;select.sh&lt;/code&gt; script is the main script.  &lt;code&gt;fixup.sh&lt;/code&gt; is called from
&lt;code&gt;select.sh&lt;/code&gt; to tweak the boot command line parameters.  I use it to add
&lt;code&gt;dom0_mem=2048M&lt;/code&gt; arguments to the boot command line so that &lt;code&gt;xen&lt;/code&gt;
reserves memory for guests.&lt;/p&gt;
&lt;h2 id=&quot;Usage%3A&quot; name=&quot;Usage%3A&quot;&gt;Usage:&lt;/h2&gt;
&lt;h3 id=&quot;Install+an+ISO+image&quot; name=&quot;Install+an+ISO+image&quot;&gt;Install an ISO image&lt;/h3&gt;
&lt;p&gt;Download a iso image from the &lt;a href=&quot;https://alpinelinux.org/&quot;&gt;alpine&lt;/a&gt; repository and enter:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sh select.sh --install &amp;lt;iso-file&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will extract the contents of the &lt;code&gt;&amp;lt;iso-file&amp;gt;&lt;/code&gt; and use &lt;code&gt;fixup.sh&lt;/code&gt;
to apply any necessary tweaks.  The new ISO file will be placed
in a directory named according to the alpine version.&lt;/p&gt;
&lt;h3 id=&quot;Enabling+a+version&quot; name=&quot;Enabling+a+version&quot;&gt;Enabling a version&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;sh select.sh &amp;lt;directory&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Makes the &lt;a href=&quot;https://alpinelinux.org/&quot;&gt;alpine&lt;/a&gt; version in &lt;code&gt;&amp;lt;directory&amp;gt;&lt;/code&gt; the current active
version for boot.&lt;/p&gt;</content>
</entry>
<entry>
<title>PulseAudio hints and tricks</title>
<link href="https://www.0ink.net/posts/2020/2020-10-03-pa-hints.html"></link>
<id>urn:uuid:baff825d-6255-93ca-656e-61bf0781e15b</id>
<updated>2024-02-02T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[PulseAudio is nowadays the default sound system in many Linux
distributions.  It lets you do a number of useful things.
PulseAudio comes with a handy command line utility pacmd to do a
number of things.
pacmd commands

...]]></summary>
<content type="html">&lt;p&gt;&lt;a href=&quot;https://www.freedesktop.org/wiki/Software/PulseAudio/&quot;&gt;PulseAudio&lt;/a&gt; is nowadays the default sound system in many Linux
distributions.  It lets you do a number of useful things.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.freedesktop.org/wiki/Software/PulseAudio/&quot;&gt;PulseAudio&lt;/a&gt; comes with a handy command line utility &lt;code&gt;pacmd&lt;/code&gt; to do a
number of things.&lt;/p&gt;
&lt;h2 id=&quot;pacmd+commands&quot; name=&quot;pacmd+commands&quot;&gt;&lt;code&gt;pacmd&lt;/code&gt; commands&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;pacmd exit&lt;/li&gt;
&lt;li&gt;pacmd help&lt;/li&gt;
&lt;li&gt;pacmd list-(modules|sinks|sources|clients|cards|samples)&lt;/li&gt;
&lt;li&gt;pacmd list-(sink-inputs|source-outputs)&lt;/li&gt;
&lt;li&gt;pacmd stat&lt;/li&gt;
&lt;li&gt;pacmd info&lt;/li&gt;
&lt;li&gt;pacmd load-module NAME [ARGS ...]&lt;/li&gt;
&lt;li&gt;pacmd unload-module NAME|#N&lt;/li&gt;
&lt;li&gt;pacmd describe-module NAME&lt;/li&gt;
&lt;li&gt;pacmd set-(sink|source)-volume NAME|#N VOLUME&lt;/li&gt;
&lt;li&gt;pacmd set-(sink-input|source-output)-volume #N VOLUME&lt;/li&gt;
&lt;li&gt;pacmd set-(sink|source)-mute NAME|#N 1|0&lt;/li&gt;
&lt;li&gt;pacmd set-(sink-input|source-output)-mute #N 1|0&lt;/li&gt;
&lt;li&gt;pacmd update-(sink|source)-proplist NAME|#N KEY=VALUE&lt;/li&gt;
&lt;li&gt;pacmd update-(sink-input|source-output)-proplist #N KEY=VALUE&lt;/li&gt;
&lt;li&gt;pacmd set-default-(sink|source) NAME|#N&lt;/li&gt;
&lt;li&gt;pacmd kill-(client|sink-input|source-output) #N&lt;/li&gt;
&lt;li&gt;pacmd play-sample NAME SINK|#N&lt;/li&gt;
&lt;li&gt;pacmd remove-sample NAME&lt;/li&gt;
&lt;li&gt;pacmd load-sample NAME FILENAME&lt;/li&gt;
&lt;li&gt;pacmd load-sample-lazy NAME FILENAME&lt;/li&gt;
&lt;li&gt;pacmd load-sample-dir-lazy PATHNAME&lt;/li&gt;
&lt;li&gt;pacmd play-file FILENAME SINK|#N&lt;/li&gt;
&lt;li&gt;pacmd dump&lt;/li&gt;
&lt;li&gt;pacmd move-(sink-input|source-output) #N SINK|SOURCE&lt;/li&gt;
&lt;li&gt;pacmd suspend-(sink|source) NAME|#N 1|0&lt;/li&gt;
&lt;li&gt;pacmd suspend 1|0&lt;/li&gt;
&lt;li&gt;pacmd set-card-profile CARD PROFILE&lt;/li&gt;
&lt;li&gt;pacmd set-(sink|source)-port NAME|#N PORT&lt;/li&gt;
&lt;li&gt;pacmd set-port-latency-offset CARD-NAME|CARD-#N PORT OFFSET&lt;/li&gt;
&lt;li&gt;pacmd set-log-target TARGET&lt;/li&gt;
&lt;li&gt;pacmd set-log-level NUMERIC-LEVEL&lt;/li&gt;
&lt;li&gt;pacmd set-log-meta 1|0&lt;/li&gt;
&lt;li&gt;pacmd set-log-time 1|0&lt;/li&gt;
&lt;li&gt;pacmd set-log-backtrace FRAMES&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Changing+audio+output+from+the+command+line&quot; name=&quot;Changing+audio+output+from+the+command+line&quot;&gt;Changing audio output from the command line&lt;/h2&gt;
&lt;p&gt;For this I use the &lt;code&gt;pacmd&lt;/code&gt; utility and manipulate the &lt;code&gt;sink&lt;/code&gt; inputs.
For already running streams, the &lt;code&gt;move-sink-input&lt;/code&gt; needs to be used.&lt;/p&gt;
&lt;p&gt;I have a PC with a weird configuration and requires me to switch
profiles instead.&lt;/p&gt;
&lt;p&gt;Get the current active profile:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pacmd list-cards | grep &#039;active profile&#039;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Set the active profile:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pacmd set-card-profile #card #profile#&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example commands:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pacmd set-card-profile 0 output:analog-stereo+input:analog-stereo
pacmd set-card-profile 0 output:hdmi-stereo+input:analog-stereo&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All that logic is in a script &lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/snippets/2020/pa-hints/patoggle&quot;&gt;here&lt;/a&gt;
or download from this &lt;a href=&quot;https://github.com/alejandroliu/0ink.net/raw/main/snippets/2020/pa-hints/patoggle&quot;&gt;link&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;MATE+control+crashing+status+icon&quot; name=&quot;MATE+control+crashing+status+icon&quot;&gt;MATE control crashing status icon&lt;/h2&gt;
&lt;p&gt;For some reason the sound control icon in the notification bar gets
lost for me.  To make it re-appear use this command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mate-volume-control-status-icon&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>Getting the current proxy pac configuration</title>
<link href="https://www.0ink.net/posts/2020/2020-10-02-windows-proxy-pac.html"></link>
<id>urn:uuid:83f36211-c0b0-70ba-c27b-71adc6d1030a</id>
<updated>2024-11-22T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[This is done using tcl for convenience.  If you
do not have it installed you can download freewrap
executable and rename freewrap.exe to wish.exe or freewrapTCLSH.exe to
tclsh.exe.
Registry Key : HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings\
REG_SZ AutoConfigURL = https://&lt;your url&gt;/proxy.pac
...]]></summary>
<content type="html">&lt;p&gt;This is done using &lt;a href=&quot;https://www.tcl.tk/&quot;&gt;tcl&lt;/a&gt; for convenience.  If you
do not have it installed you can download &lt;a href=&quot;http://freewrap.sourceforge.net/&quot;&gt;freewrap&lt;/a&gt;
executable and rename &lt;code&gt;freewrap.exe&lt;/code&gt; to &lt;code&gt;wish.exe&lt;/code&gt; or &lt;code&gt;freewrapTCLSH.exe&lt;/code&gt; to
&lt;code&gt;tclsh.exe&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Registry Key : HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings\
REG_SZ AutoConfigURL = https://&amp;lt;your url&amp;gt;/proxy.pac
REG_DWORD ProxyEnable = 0&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the &lt;a href=&quot;https://www.tcl.tk/&quot;&gt;tcl&lt;/a&gt; script:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tcl&quot;&gt;package require http

set pacURL [registry get {HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings} AutoConfigURL]
puts $pacURL

set conn [::http::geturl $pacURL]
::http::wait $conn
puts [::http::data $conn]
&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>Definiton of maturity</title>
<link href="https://www.0ink.net/posts/2020/2020-04-06-maturity.html"></link>
<id>urn:uuid:b5a5e56b-495c-ca3d-39de-b5b9dc2aad21</id>
<updated>2024-11-22T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[Maturity is:
The ability to stick with a job until it’s finished.
The ability to do a job without being supervised.
The ability to carry money without spending it.
And the ability to bear an injustice without wanting to get even.
...]]></summary>
<content type="html">&lt;p&gt;Maturity is:&lt;/p&gt;
&lt;p&gt;The ability to stick with a job until it’s finished.&lt;/p&gt;
&lt;p&gt;The ability to do a job without being supervised.&lt;/p&gt;
&lt;p&gt;The ability to carry money without spending it.&lt;/p&gt;
&lt;p&gt;And the ability to bear an injustice without wanting to get even.&lt;/p&gt;</content>
</entry>
<entry>
<title>Using XScreenSaver Hacks with mate-screensaver</title>
<link href="https://www.0ink.net/posts/2020/2020-03-19-mate-screensaver-hacks.html"></link>
<id>urn:uuid:c06d6c36-fe1c-d081-8914-bbfbfe5ca5a8</id>
<updated>2024-02-02T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Here we explain how to use XScreenSaver EXCELLENT
screensaver hack collection with the MATE screensaver
applet.

Install xscreensaver and mate-screensaver
On my linux distribution this creates the following directories:
...]]></summary>
<content type="html">&lt;p&gt;Here we explain how to use &lt;a href=&quot;https://www.jwz.org/xscreensaver/&quot;&gt;XScreenSaver&lt;/a&gt; &lt;strong&gt;EXCELLENT&lt;/strong&gt;
screensaver hack collection with the &lt;a href=&quot;https://mate-desktop.org/&quot;&gt;MATE&lt;/a&gt; screensaver
applet.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Install &lt;code&gt;xscreensaver&lt;/code&gt; and &lt;code&gt;mate-screensaver&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;On my linux distribution this creates the following directories:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/usr/libexec/xscreensaver&lt;/code&gt;: contains the screensaver hacks executables&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/usr/libexec/mate-screensaver&lt;/code&gt; : contains the &lt;code&gt;mate-screensaver&lt;/code&gt; executables&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/usr/share/applications/screensavers&lt;/code&gt; : containes the &lt;code&gt;dekstop&lt;/code&gt; files&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Create a small script that will call the screensaver hack with the right
arguments.  Make sure this script is in the &lt;code&gt;/usr/libexec/mate-screensaver&lt;/code&gt;
directory, as the &lt;code&gt;mate-screensaver&lt;/code&gt; preferences will not accept any
executables that are not in the right places.&lt;/li&gt;
&lt;li&gt;Create a desktop file to call the screensaver hack.  Verify that
the &lt;code&gt;Exec&lt;/code&gt; property contains the application with the right arguments
and the &lt;code&gt;TryExec&lt;/code&gt; only contains a path to the script that you created
in the previous step.  The &lt;code&gt;mate-screensaver&lt;/code&gt; preferences applet
will test if the file specified in &lt;code&gt;TryExec&lt;/code&gt; is indeed executable.&lt;/li&gt;
&lt;li&gt;Restart &lt;code&gt;mate-screensaver&lt;/code&gt;.  I usually logout and log back in.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For my computers I use this script:&lt;/p&gt;
&lt;script src=&quot;https://tortugalabs.github.io/embed-like-gist/embed.js?style=github&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showCopy=on&amp;amp;fetchFromJsDelivr=on&amp;amp;target=https://github.com/alejandroliu/0ink.net/blob/main/snippets/2020/mate-screensaver-hacks/installer.sh&quot;&gt;&lt;/script&gt;
&lt;p&gt;This simplifies the full process.  Just run the script (you may need to
&lt;code&gt;sudo&lt;/code&gt;) with the following options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$0 hacks [-e\|-d]&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;shows the list of hacks and its enabled or disabled status.&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;-e&lt;/code&gt; option will only show enabled hacks.&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;-d&lt;/code&gt; option will only show disabled hacks.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;`$0 enable [--all|hacks]
&lt;ul&gt;
&lt;li&gt;enable the specified hacks.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;--all&lt;/code&gt; to enable all available hacks (excluding blacklisted hacks)&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;`$0 disable [--all|hacks]
&lt;ul&gt;
&lt;li&gt;disable the specified hacks.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;--all&lt;/code&gt; to disable all available hacks&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;If you like &lt;a href=&quot;https://www.jwz.org/xscreensaver/&quot;&gt;XScreenSaver&lt;/a&gt; and would like to see the same software
on Windows, you should read &lt;a href=&quot;https://www.jwz.org/xscreensaver/xscreensaver-windows.html&quot;&gt;this article&lt;/a&gt; from the
&lt;a href=&quot;https://www.jwz.org/xscreensaver/&quot;&gt;XScreenSaver&lt;/a&gt; author.&lt;/p&gt;</content>
</entry>
<entry>
<title>nginx&#039;s auth_request_module howto</title>
<link href="https://www.0ink.net/posts/2019/2019-05-10-nginx_mod_authrequest.html"></link>
<id>urn:uuid:04ee8311-2ecb-ecd0-b7bc-cb5ab29ecc9d</id>
<updated>2024-02-02T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This article tries to supplement the nginx documentations
regarding the auth_request module
and how to configure it.  In my opinion, that documentation
is a bit incomplete.
What is the nginx's auth_request module
The documentation for this module says, it implements client
...]]></summary>
<content type="html">&lt;p&gt;This article tries to supplement the &lt;a href=&quot;http://nginx.org/en/&quot;&gt;nginx&lt;/a&gt; documentations
regarding the &lt;a href=&quot;http://nginx.org/en/docs/http/ngx_http_auth_request_module.html&quot;&gt;auth_request&lt;/a&gt; module
and how to &lt;a href=&quot;https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-subrequest-authentication/&quot;&gt;configure&lt;/a&gt; it.  In my opinion, that documentation
is a bit incomplete.&lt;/p&gt;
&lt;h2 id=&quot;What+is+the+nginx%27s+%5Bauth_request%5D%5Bngx_http_auth_request_module%5D+module&quot; name=&quot;What+is+the+nginx%27s+%5Bauth_request%5D%5Bngx_http_auth_request_module%5D+module&quot;&gt;What is the nginx&#039;s &lt;a href=&quot;http://nginx.org/en/docs/http/ngx_http_auth_request_module.html&quot;&gt;auth_request&lt;/a&gt; module&lt;/h2&gt;
&lt;p&gt;The documentation for this module says, it implements client
authorization based on the result of a subrequest.&lt;/p&gt;
&lt;p&gt;This means that when you make an HTTP request to a protected URL,
&lt;a href=&quot;http://nginx.org/en/&quot;&gt;nginx&lt;/a&gt; performs an internal subrequest to a defined
authorization URL. If the result of the subrequest is HTTP 2xx,
&lt;a href=&quot;http://nginx.org/en/&quot;&gt;nginx&lt;/a&gt; proxies the original HTTP request to the backend server.
If the result of the subrequest is HTTP 401 or 403, access to the
backend server is denied.&lt;/p&gt;
&lt;p&gt;By configuring &lt;a href=&quot;http://nginx.org/en/&quot;&gt;nginx&lt;/a&gt;, you can redirect those 401s or 403s to
a login page where the user is authenticated and then redirected to
the original destination.&lt;/p&gt;
&lt;p&gt;The entire authorization subrequest process is then repeated, but
because the user is now authenticated the subrequest returns HTTP 200
and the original HTTP request is proxied to the backend server.&lt;/p&gt;
&lt;h2 id=&quot;Configuring+%5Bnginx%5D%5Bnginx%5D&quot; name=&quot;Configuring+%5Bnginx%5D%5Bnginx%5D&quot;&gt;Configuring &lt;a href=&quot;http://nginx.org/en/&quot;&gt;nginx&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In your &lt;a href=&quot;http://nginx.org/en/&quot;&gt;nginx&lt;/a&gt; configuration...&lt;/p&gt;
&lt;p&gt;This block configures the web server area that will be protected:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nginx&quot;&gt;        location /hello {
          error_page 401 = @error401;   # Specific login page to use
          auth_request /auth;       # The sub-request to use
          auth_request_set $username $upstream_http_x_username; # Make the sub request data available
          auth_request_set $sid $upstream_http_x_session;   # send what is needed

          proxy_pass http://sample.com:8080/hello;  # actual location of protected data
          proxy_set_header X-Forwarded-Host $host;  # Custom headers with authentication related data
          proxy_set_header X-Remote-User $username;
          proxy_set_header X-Remote-SID $sid;
        }

    location @error401 {
          return 302 /login/?url=http://$http_host$request_uri;
        }
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;error_page 401&lt;/code&gt; defines the custom login page to use (if any).
In theory, for a REST API, would be possible to authenticate
using a provided &lt;code&gt;token&lt;/code&gt; header which makes the login page
unnecesary.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;auth_request /auth&lt;/code&gt; defines that this location needs authentication
and defines the sub-request location to use.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;auth_request_set&lt;/code&gt; can be used to get data from the subrequest
headers and make it available later (like for instance, to a
backend content server using custom headers.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;proxy_pass&lt;/code&gt; defines the actual backend content server.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;proxy_set_header&lt;/code&gt; defines custom header used to pass information
to the content backend server.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This block configures the authentication sub-request server:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nginx&quot;&gt;        location = /auth {
          proxy_pass http://auth-server.sample.com:8080/auth;   # authentication server
          proxy_pass_request_body off;              # no data is being transferred...
          proxy_set_header Content-Length &#039;0&#039;;
          proxy_set_header Host $host;              # Custom headers with authentication related data
          proxy_set_header X-Origin-URI $request_uri;
          proxy_set_header X-Forwarded-Host $host;
        }&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;proxy_pass&lt;/code&gt; where the sub request should be handled.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;proxy_pass_request_body off&lt;/code&gt; and &lt;code&gt;proxy_set_header Content-Length 0&lt;/code&gt; are
used to supress the content body and only sends the headers to the
authentication server.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;proxy_set_header&lt;/code&gt; additional details being send to the sub request.
For example, the &lt;code&gt;X-Origin-URI&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This implements the login pager URL&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nginx&quot;&gt;        # If the user is not logged in, redirect them to login URL
        location @error401 {
          return 302 https://$host/login/?url=https://$http_host$request_uri;
        }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example, the login page is on the same reverse proxy, but
it doesn&#039;t have to be that way.&lt;/p&gt;
&lt;p&gt;The actual login page:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nginx&quot;&gt;        location /login/ {
          proxy_pass http://auth-server.sample.com:8080/login/; # Where the login happens
          proxy_set_header X-My-Real-IP $remote_addr;       # Additional parameters to send to login page
          proxy_set_header X-My-Real-Port $remote_port;
          proxy_set_header X-My-Server-Port $server_port;
        }&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;proxy_pass&lt;/code&gt; points to where the login script runs&lt;/li&gt;
&lt;li&gt;&lt;code&gt;proxy_set_header&lt;/code&gt; can be used to pass additional fields that may
be needed by the login script.  To implement for example,
&lt;code&gt;$remote_addr&lt;/code&gt; based access rules.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So in this particular example, we are referring to a server with
&lt;strong&gt;TWO&lt;/strong&gt; locations:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;http://auth-server.sample.com:8080/auth&lt;/code&gt; - The sub-request URI which is not visible
outside but handles the sub-request.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;http://auth-server.sample.com:8080/login/&lt;/code&gt; - The login URI which handles the
login conversation.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;The+Authentication+Server&quot; name=&quot;The+Authentication+Server&quot;&gt;The Authentication Server&lt;/h2&gt;
&lt;p&gt;This is where the &lt;a href=&quot;http://nginx.org/en/&quot;&gt;nginx&lt;/a&gt; documentation falls a bit short, there
is no actual authentication server example to refer to.&lt;/p&gt;
&lt;p&gt;In my example, we have a simple authentication workflow.  When an
unauthenticated user hits the server, the sub-request is called
and checks (and fails) for a session cookie.&lt;/p&gt;
&lt;p&gt;The user is then re-directed to the login page, where the actual
login takes place.  If succesful, a session cookie is set and
the user is redirected to the original URL.&lt;/p&gt;
&lt;p&gt;This is implemented using the following script:&lt;/p&gt;
&lt;script src=&quot;https://tortugalabs.github.io/embed-like-gist/embed.js?style=github&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showCopy=on&amp;amp;fetchFromJsDelivr=on&amp;amp;target=https://github.com/alejandroliu/0ink.net/blob/main/snippets/2019/nginx_mod_authrequest/auth1.py&quot;&gt;&lt;/script&gt;
&lt;p&gt;This makes uses of the &lt;a href=&quot;https://bottlepy.org/&quot;&gt;bottle&lt;/a&gt; micro framework.&lt;/p&gt;
&lt;p&gt;It implements four routes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;GET /hello&lt;/code&gt;
This is just a demo URL used for testing.  Only shows the request headers.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GET /login/&lt;/code&gt;
This is the login page entry point.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST /login/&lt;/code&gt;
This is the handler for the login page.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GET /auth&lt;/code&gt;
This is the sub-request handler.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For the demo, we are not really doing any login handling.  You only
need to make the username the same as the password to login.  Anything
else is a login failure.&lt;/p&gt;
&lt;p&gt;When the user succesfully logs in, we set a cookie.  Because the
login URL and the protected resource (&lt;code&gt;/hello&lt;/code&gt; URL) are in the
same cookie scope, we can use cookie set by the login page
as the verification token in the sub-request.&lt;/p&gt;
&lt;p&gt;Note that the login page can be as simple or as complex as it is
needed.  For example, it is possible to implement a &lt;a href=&quot;https://en.wikipedia.org/wiki/Security_Assertion_Markup_Language&quot;&gt;SAML&lt;/a&gt;,
&lt;a href=&quot;https://openid.net/connect/&quot;&gt;OpenID Connect&lt;/a&gt;, or any Single-Sign-On workflow
available.&lt;/p&gt;
&lt;p&gt;Alternatively, two factor authentication could be implemented here.
The possibilities are endless.&lt;/p&gt;
&lt;p&gt;An interesting use of the &lt;a href=&quot;http://nginx.org/en/docs/http/ngx_http_auth_request_module.html&quot;&gt;auth_request&lt;/a&gt;
module would be to delegate &lt;a href=&quot;https://en.wikipedia.org/wiki/Basic_access_authentication&quot;&gt;Basic Authentication&lt;/a&gt; to a different
server or to even implement authentications not supported by
&lt;a href=&quot;http://nginx.org/en/&quot;&gt;nginx&lt;/a&gt; like for example a simple Token-Bearer header or
&lt;a href=&quot;https://en.wikipedia.org/wiki/Digest_access_authentication&quot;&gt;Digest authentication&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;basic+authentication&quot; name=&quot;basic+authentication&quot;&gt;basic authentication&lt;/h3&gt;
&lt;p&gt;This may seem silly since &lt;a href=&quot;http://nginx.org/en/&quot;&gt;nginx&lt;/a&gt; supports &lt;a href=&quot;https://en.wikipedia.org/wiki/Basic_access_authentication&quot;&gt;basic authentication&lt;/a&gt;
out of the box.  The use case for this is when you have a cluster of nginx
front ends, and you want all of them to authenticate against a central
identity server.  Furthermore, since the URI can be passed, a more sophisticated
access control can be implemented.  Finally, additional values can be passed
through headers, such as group names, tokens, etc.&lt;/p&gt;
&lt;h4 id=&quot;nginx+configuration&quot; name=&quot;nginx+configuration&quot;&gt;nginx configuration&lt;/h4&gt;
&lt;p&gt;Protected Resource:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nginx&quot;&gt;        location /hello {
          auth_request /auth;       # The sub-request to use
          auth_request_set $username $upstream_http_x_username; # Make the sub request data available

          proxy_pass http://sample.com:8080/hello;  # actual location of protected data
          proxy_set_header X-Forwarded-Host $host;  # Custom headers with authentication related data
          proxy_set_header X-Remote-User $username;
        }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; unlike the previous example, we do not need to provide a @error401 page.&lt;/p&gt;
&lt;p&gt;Sub-request configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nginx&quot;&gt;        location = /auth {
          proxy_pass http://auth-server.sample.com:8080/auth;   # authentication server
          proxy_pass_request_body off;              # no data is being transferred...
          proxy_set_header Content-Length &#039;0&#039;;
          proxy_set_header Host $host;              # Custom headers with authentication related data
          proxy_set_header X-Origin-URI $request_uri;
          proxy_set_header X-Forwarded-Host $host;
        }&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;authentication+server&quot; name=&quot;authentication+server&quot;&gt;authentication server&lt;/h4&gt;
&lt;p&gt;The python implementation (again, using &lt;a href=&quot;https://bottlepy.org/&quot;&gt;bottle&lt;/a&gt;):&lt;/p&gt;
&lt;script src=&quot;https://tortugalabs.github.io/embed-like-gist/embed.js?style=github&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showCopy=on&amp;amp;fetchFromJsDelivr=on&amp;amp;target=https://github.com/alejandroliu/0ink.net/blob/main/snippets/2019/nginx_mod_authrequest/auth2.py&quot;&gt;&lt;/script&gt;
&lt;p&gt;Like in the previous example, we are not doing any user/password verification.  We are only
checking if username and password are matching.&lt;/p&gt;
&lt;p&gt;Unlike the previous example, all the authentication is handled by a single route (&lt;code&gt;/auth&lt;/code&gt;).
It returns &#039;WWW-Authenticate&#039; to prompt the user for a password.  And if it sees
an &lt;code&gt;Authorization&lt;/code&gt; header, it would validate it.&lt;/p&gt;
&lt;h3 id=&quot;digest+authentication&quot; name=&quot;digest+authentication&quot;&gt;digest authentication&lt;/h3&gt;
&lt;p&gt;This implements &lt;a href=&quot;https://en.wikipedia.org/wiki/Digest_access_authentication&quot;&gt;digest&lt;/a&gt; authentication for &lt;a href=&quot;http://nginx.org/en/&quot;&gt;nginx&lt;/a&gt; using the
&lt;a href=&quot;http://nginx.org/en/docs/http/ngx_http_auth_request_module.html&quot;&gt;auth request module&lt;/a&gt;.  The nginx configuration is the
same as in the &lt;a href=&quot;https://en.wikipedia.org/wiki/Basic_access_authentication&quot;&gt;Basic&lt;/a&gt; authentication.&lt;/p&gt;
&lt;p&gt;The implentation in python (using &lt;a href=&quot;https://bottlepy.org/&quot;&gt;bottle&lt;/a&gt; framework):&lt;/p&gt;
&lt;script src=&quot;https://tortugalabs.github.io/embed-like-gist/embed.js?style=github&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showCopy=on&amp;amp;fetchFromJsDelivr=on&amp;amp;target=https://github.com/alejandroliu/0ink.net/blob/main/snippets/2019/nginx_mod_authrequest/auth3.py&quot;&gt;&lt;/script&gt;</content>
</entry>
<entry>
<title>Python Virtual Environments</title>
<link href="https://www.0ink.net/posts/2019/2019-05-01-python-venv.html"></link>
<id>urn:uuid:bee0a0b1-4182-1d9f-3165-9646f36952cb</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This is the least you need to know to get to use a Python virtual
environment.
What is a Virtual Environment
At its core, the main purpose of Python virtual environments is to
create an isolated environment for Python projects. This means that
each project can have its own dependencies, regardless of what
...]]></summary>
<content type="html">&lt;p&gt;This is the least you need to know to get to use a Python virtual
environment.&lt;/p&gt;
&lt;h3 id=&quot;What+is+a+Virtual+Environment&quot; name=&quot;What+is+a+Virtual+Environment&quot;&gt;What is a Virtual Environment&lt;/h3&gt;
&lt;p&gt;At its core, the main purpose of Python virtual environments is to
create an isolated environment for Python projects. This means that
each project can have its own dependencies, regardless of what
dependencies every other project has.&lt;/p&gt;
&lt;p&gt;The great thing about this is that there are no limits to the number
of environments you can have since they&#039;re just directories containing
a few scripts.&lt;/p&gt;
&lt;h3 id=&quot;Pre-requisites&quot; name=&quot;Pre-requisites&quot;&gt;Pre-requisites&lt;/h3&gt;
&lt;p&gt;While &lt;code&gt;venv&lt;/code&gt; is part of &lt;code&gt;python3&lt;/code&gt;, for &lt;code&gt;python2&lt;/code&gt; you need to install
&lt;code&gt;virtualenv&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;void-linux: &lt;code&gt;python-virtualenv&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Create&quot; name=&quot;Create&quot;&gt;Create&lt;/h3&gt;
&lt;p&gt;To create a new virtual environment:&lt;/p&gt;
&lt;h4 id=&quot;Python2&quot; name=&quot;Python2&quot;&gt;Python2&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;mkdir folder
virtualenv folder&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you want to inherit system global packages in your virtual
environment use this instead:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir folder
virtualenv --system-site-packages folder&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;Python3&quot; name=&quot;Python3&quot;&gt;Python3&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;mkdir folder
python3 -m venv folder&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you want to inherit system global packages in your virtual
environment use this instead:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir folder
python3 -m venv --system-site-packages folder&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I prefer to use the &lt;code&gt;--system-site-packages&lt;/code&gt; option, that way
I can have &lt;code&gt;binary&lt;/code&gt; modules using the host&#039;s package manager.
This is in order to avoid having a compiler in the host system.&lt;/p&gt;
&lt;h3 id=&quot;Activate&quot; name=&quot;Activate&quot;&gt;Activate&lt;/h3&gt;
&lt;p&gt;To activate a virtual environment:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;. &amp;lt;folder&amp;gt;/bin/activate&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pay attention that we are using &lt;code&gt;.&lt;/code&gt; to source the script in
the current interpreter.&lt;/p&gt;
&lt;h3 id=&quot;De-Activate&quot; name=&quot;De-Activate&quot;&gt;De-Activate&lt;/h3&gt;
&lt;p&gt;To de-activate:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;deactivate&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Run+from+a+script&quot; name=&quot;Run+from+a+script&quot;&gt;Run from a script&lt;/h3&gt;
&lt;p&gt;To use the virtual environment from a script (i.e. running
as a background daemon) you need to add these to the
beginning of your python script:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;activate_this = &#039;/path/to/virtualenv/bin/activate_this.py&#039;
execfile(activate_this, dict(__file__=activate_this))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or from a shell cript:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/sh
source name_Env/bin/activate
# virtualenv is now active.
exec python script.py &quot;$@&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;References&quot; name=&quot;References&quot;&gt;References&lt;/h3&gt;
&lt;p&gt;For more information see:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.python.org/3/library/venv.html&quot;&gt;Python3 venv&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.python-guide.org/dev/virtualenvs/&quot;&gt;pipenv &amp;amp; virtual environments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://realpython.com/python-virtual-environments-a-primer/&quot;&gt;virtual env primer&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Secure erase of disc drives</title>
<link href="https://www.0ink.net/posts/2019/2019-04-20-secure-erase.html"></link>
<id>urn:uuid:c388ce5d-4e05-f17c-9fd0-937b716fb3cc</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This article is about erasing disc drives securely.  Specially for SSD
drives, writing zeros or random data to discs is not good enough and
counterproductive.
One way to do secure erase (for disposal) is to begin with an encrypted
disc.  However, after the fact the following options are possible:
ATA Secure Erase
...]]></summary>
<content type="html">&lt;p&gt;This article is about erasing disc drives securely.  Specially for SSD
drives, writing zeros or random data to discs is not good enough and
counterproductive.&lt;/p&gt;
&lt;p&gt;One way to do secure erase (for disposal) is to begin with an encrypted
disc.  However, after the fact the following options are possible:&lt;/p&gt;
&lt;h2 id=&quot;ATA+Secure+Erase&quot; name=&quot;ATA+Secure+Erase&quot;&gt;ATA Secure Erase&lt;/h2&gt;
&lt;p&gt;You should use the drive&#039;s security erase feature.&lt;/p&gt;
&lt;p&gt;Make sure the drive Security is not frozen. If it is, it may help to suspend and resume the computer.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo hdparm -I /dev/sdX | grep frozen
       not     frozen &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The (filtered) command output means that this drive is ”not frozen” and you can continue.&lt;/p&gt;
&lt;p&gt;Set a User Password (this password is cleared too, the exact choice does not matter).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo hdparm --user-master u --security-set-pass Eins /dev/sdX&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Issue the ATA Secure Erase command&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo hdparm --user-master u --security-erase Eins /dev/sdX&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;/dev/sdX is the SSD as a block device that you want to erase.&lt;/li&gt;
&lt;li&gt;Eins is the password chosen in this example.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See the ATA Secure Erase article in the &lt;a href=&quot;https://ata.wiki.kernel.org/index.php/ATA_Secure_Erase&quot;&gt;Linux kernel wiki&lt;/a&gt; for
complete instructions including troubleshooting.&lt;/p&gt;
&lt;p&gt;If for some reason you need to remove the password use:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo hdparm --security-disable Eins&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;blkdiscard&quot; name=&quot;blkdiscard&quot;&gt;blkdiscard&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;util-linux 2.23&lt;/code&gt; offers &lt;a href=&quot;http://man7.org/linux/man-pages/man8/blkdiscard.8.html&quot;&gt;blkdiscard&lt;/a&gt; which discards data without
secure-wiping them.  This has been tested to work over SATA and mmcblk
but not USB.&lt;/p&gt;
&lt;p&gt;An excerpt from the manual page of &lt;code&gt;blkdiscard(8)&lt;/code&gt;:&lt;/p&gt;
&lt;hr /&gt;
&lt;h3 id=&quot;NAME&quot; name=&quot;NAME&quot;&gt;NAME&lt;/h3&gt;
&lt;p&gt;blkdiscard - discard sectors on a device&lt;/p&gt;
&lt;h3 id=&quot;SYNOPSIS&quot; name=&quot;SYNOPSIS&quot;&gt;SYNOPSIS&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;blkdiscard [-o offset] [-l length] [-s] [-v] device&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;DESCRIPTION&quot; name=&quot;DESCRIPTION&quot;&gt;DESCRIPTION&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;blkdiscard&lt;/code&gt; is used to discard device sectors. This is useful for
solid-state drivers (SSDs) and thinly-provisioned storage. Unlike
&lt;code&gt;fstrim(8)&lt;/code&gt; this command is used directly on the block device.&lt;/p&gt;
&lt;p&gt;By default, &lt;code&gt;blkdiscard&lt;/code&gt; will discard all blocks on the device. Options
may be used to modify this behavior based on range or size, as explained
below.&lt;/p&gt;
&lt;p&gt;The device argument is the pathname of the block device.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;WARNING: All data in the discarded region on the device will be lost!&lt;/strong&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;Use+TRIM&quot; name=&quot;Use+TRIM&quot;&gt;Use TRIM&lt;/h2&gt;
&lt;p&gt;To enable TRIM:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo vi /etc/fstab&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Change &lt;code&gt;ext4 errors=remount-ro 0&quot; into &quot;ext4 discard,errors=remount-ro 0&lt;/code&gt;.
&lt;strong&gt;(Add discard)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Save and reboot, TRIM should now be enabled.&lt;/p&gt;
&lt;p&gt;Check if TRIM is enabled:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo dd if=/dev/urandom of=tempfile count=100 bs=512k oflag=direct
sudo hdparm --fibmap tempfile&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use the first begin_LBA address.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hdparm --read-sector [begin_LBA] /dev/sda&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now it should return numbers and characters. Remove the file and sync.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rm tempfile
sync&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, run the following command again. If it returns zeros TRIM is enabled.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hdparm --read-sector [begin_LBA] /dev/sda&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another option is to use the &lt;a href=&quot;http://man7.org/linux/man-pages/man8/fstrim.8.html&quot;&gt;fstrim&lt;/a&gt; command.&lt;/p&gt;
&lt;h2 id=&quot;Old+fashioned+writes&quot; name=&quot;Old+fashioned+writes&quot;&gt;Old fashioned writes&lt;/h2&gt;
&lt;p&gt;This is what I used to do for magnetic discs.  Note, that this is
discouraged for SSD devices:&lt;/p&gt;
&lt;p&gt;First I create some random data to use:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dd if=/dev/urandom of=/var/tmp/random bs=1M count=128&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we write random data to disc:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(while : ; do dd if=/var/tmp/random bs=4k ; done ) | pv | dd of=/dev/sdX bs=4k&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;pv&lt;/code&gt; part of the pipe is &lt;strong&gt;optional&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Afterwards:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dd if=/dev/zero of=/dev/sdX bs=4k&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>Co-existing GLIBC binaries with Void-Linux MUSL edition</title>
<link href="https://www.0ink.net/posts/2019/2019-04-10-void-musl-with-glibc.html"></link>
<id>urn:uuid:1ce9acd2-8501-8503-4e5e-89ed1ac4f0d6</id>
<updated>2025-01-01T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[I am running void-linux at home with musl as the standard
C library.
While most things work well, there is a number of programs that
do not and must be using glibc counterparts.
To enable this I followed this guide here: Live switching Void Linux from glibc to musl.
To set-up:
...]]></summary>
<content type="html">&lt;p&gt;I am running &lt;a href=&quot;https://voidlinux.org/&quot;&gt;void-linux&lt;/a&gt; at home with &lt;code&gt;musl&lt;/code&gt; as the standard
C library.&lt;/p&gt;
&lt;p&gt;While most things work well, there is a number of programs that
do not and must be using &lt;code&gt;glibc&lt;/code&gt; counterparts.&lt;/p&gt;
&lt;p&gt;To enable this I followed this guide here: &lt;a href=&quot;https://blog.w1r3.net/2017/09/23/live-switching-void-linux-from-glibc-to-musl.html&quot;&gt;Live switching Void Linux from glibc to musl&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To set-up:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo mkdir -p /glibc
sudo env XBPS_ARCH=x86_64 xbps-install --repository=https://repo-default.voidlinux.org/current -r /glibc -S base-voidstrap&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sudo&lt;/code&gt; : yes, we need root&lt;/li&gt;
&lt;li&gt;&lt;code&gt;env&lt;/code&gt; : Needed because we are using &lt;code&gt;sudo&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;XBPS_ARCH=x86_64&lt;/code&gt; : architecture to use.  Since we are using musl,
we point to the glibc version.  It should be possible to create
a 32 bit root here.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--repository=https://repo-default.voidlinux.org/current&lt;/code&gt; : the repository
to use.  Feel free to replace to something closer.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-r /glibc&lt;/code&gt; :  directory tree where the glibc executables will live&lt;/li&gt;
&lt;li&gt;&lt;code&gt;base-voidstrap&lt;/code&gt; : unlike &lt;code&gt;base-system&lt;/code&gt;, this meta-package is normally
used for containers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To keep this tree up to date:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo env XBPS_ARCH=x86_64 xbps-install --repository=https://repo-default.voidlinux.org/current -r /glibc -Su&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To add software to the tree:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo env XBPS_ARCH=x86_64 xbps-install --repository=https://repo-default.voidlinux.org/current  -r /glibc -S pkg&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once this is set-up you need a small program to kick off the &lt;code&gt;glibc&lt;/code&gt; executables.  I copied this one:&lt;/p&gt;
&lt;script src=&quot;https://tortugalabs.github.io/embed-like-gist/embed.js?style=github&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showCopy=on&amp;amp;fetchFromJsDelivr=on&amp;amp;target=https://github.com/alejandroliu/0ink.net/blob/main/snippets/2019/void-glibc-in-musl/glibc.c&quot;&gt;&lt;/script&gt;
&lt;p&gt;To compile and install:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gcc -s -o glibc glibc.c
sudo cp glibc /usr/bin
sudo chown root:root /usr/bin/glibc
sudo chmod +sx /usr/bin/glibc&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then you can just run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;glibc cmd args&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The following software I have found doesn&#039;t work well using &lt;code&gt;musl&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Calibre&lt;/li&gt;
&lt;li&gt;building buildroot (Because of compilation of &lt;code&gt;fakeroot&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Calculate system availability</title>
<link href="https://www.0ink.net/posts/2019/2019-04-01-availability-calculations.html"></link>
<id>urn:uuid:1ff19c62-43b1-21fd-34b5-7e1e36ab87c2</id>
<updated>2024-11-22T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[To calculate the availability of redundant systems you can
use this formula:
total_avail = 1-(1 - single_avail) ^ (number_of_nodes)


  Nodes: 
...]]></summary>
<content type="html">&lt;p&gt;To calculate the availability of redundant systems you can
use this formula:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;total_avail = 1-(1 - single_avail) ^ (number_of_nodes)&lt;/code&gt;&lt;/pre&gt;
&lt;form action=&quot;&quot;&gt;
&lt;table&gt;
  &lt;tr&gt;&lt;td&gt;Nodes:&lt;/td&gt;&lt;td&gt; &lt;input type=&quot;number&quot; id=&quot;nodes&quot; name=&quot;nodes&quot; min=&quot;1&quot; maxlength=&quot;4&quot; value=&quot;2&quot; onchange=&quot;myCalculation();&quot;&gt;&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;Single component availability (%):&lt;/td&gt;&lt;td&gt;&lt;input type=&quot;number&quot; id=&quot;savail&quot; name=&quot;savail&quot; min=&quot;0.10&quot; step=&quot;any&quot; maxlength=&quot;10&quot; value=&quot;99.00&quot; onchange=&quot;myCalculation();&quot;&gt;&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;Total Availability (%): &lt;/td&gt;&lt;td&gt;&lt;input name=&quot;total&quot; id=&quot;total&quot; type=&quot;number&quot; maxlength=&quot;20&quot; min=&quot;0&quot; placeholder=&quot;00.00&quot; readonly&gt; &lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;
&lt;/form&gt;
&lt;script&gt;
function myCalculation() {
var nodes = parseInt(document.getElementById(&#039;nodes&#039;).value,10);
var sava = parseFloat(document.getElementById(&#039;savail&#039;).value);
var result = (1-(1-sava/100.0)**(nodes))*100
document.getElementById(&#039;total&#039;).value = result
}
&lt;/script&gt;</content>
</entry>
<entry>
<title>Ad-Hoc rsync daemons</title>
<link href="https://www.0ink.net/posts/2019/2019-03-20-adhoc-rsync.html"></link>
<id>urn:uuid:a0575684-65b1-2b27-8ca5-03406821a705</id>
<updated>2024-02-02T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[The other day I needed to copy a bunch of files between to servers
in my home network.  Because of the volume I wanted to copy the files
without having to go through ssh's encryption overhead.  So I
figured I could use netcat for the data transport.
To do that I wrote these short scripts.
Remote scripts
...]]></summary>
<content type="html">&lt;p&gt;The other day I needed to copy a bunch of files between to servers
in my home network.  Because of the volume I wanted to copy the files
without having to go through &lt;code&gt;ssh&lt;/code&gt;&#039;s encryption overhead.  So I
figured I could use &lt;code&gt;netcat&lt;/code&gt; for the data transport.&lt;/p&gt;
&lt;p&gt;To do that I wrote these short scripts.&lt;/p&gt;
&lt;h2 id=&quot;Remote+scripts&quot; name=&quot;Remote+scripts&quot;&gt;Remote scripts&lt;/h2&gt;
&lt;p&gt;Copy these scripts on the remote server.  Make sure they are executable.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Remote CLI&lt;/li&gt;
&lt;/ul&gt;
&lt;script src=&quot;https://tortugalabs.github.io/embed-like-gist/embed.js?style=github&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showCopy=on&amp;amp;fetchFromJsDelivr=on&amp;amp;target=https://github.com/alejandroliu/0ink.net/blob/main/snippets/2019/adhoc-rsync/recv-nc&quot;&gt;&lt;/script&gt;
&lt;ul&gt;
&lt;li&gt;Remote Helper script&lt;/li&gt;
&lt;/ul&gt;
&lt;script src=&quot;https://tortugalabs.github.io/embed-like-gist/embed.js?style=github&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showCopy=on&amp;amp;fetchFromJsDelivr=on&amp;amp;target=https://github.com/alejandroliu/0ink.net/blob/main/snippets/2019/adhoc-rsync/recv&quot;&gt;&lt;/script&gt;
&lt;h2 id=&quot;Local+scripts&quot; name=&quot;Local+scripts&quot;&gt;Local scripts&lt;/h2&gt;
&lt;p&gt;Copy these scripts on the local server.  Make sure they are executable&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Local CLI&lt;/li&gt;
&lt;/ul&gt;
&lt;script src=&quot;https://tortugalabs.github.io/embed-like-gist/embed.js?style=github&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showCopy=on&amp;amp;fetchFromJsDelivr=on&amp;amp;target=https://github.com/alejandroliu/0ink.net/blob/main/snippets/2019/adhoc-rsync/send&quot;&gt;&lt;/script&gt;
&lt;ul&gt;
&lt;li&gt;Local Helper Script&lt;/li&gt;
&lt;/ul&gt;
&lt;script src=&quot;https://tortugalabs.github.io/embed-like-gist/embed.js?style=github&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showCopy=on&amp;amp;fetchFromJsDelivr=on&amp;amp;target=https://github.com/alejandroliu/0ink.net/blob/main/snippets/2019/adhoc-rsync/send-nc&quot;&gt;&lt;/script&gt;
&lt;h2 id=&quot;Usage&quot; name=&quot;Usage&quot;&gt;Usage&lt;/h2&gt;
&lt;p&gt;Usage is fairly straight forward, on the remote server enter the
command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./recv-nc&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will make remote listen for new client connects.  On the local
server, enter the command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./send -avzr --delete --stats SRC/ remote:DST&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Actually, just use whatever &lt;code&gt;rsync&lt;/code&gt; options you need to use.  The &lt;code&gt;send&lt;/code&gt;
script will include the &lt;code&gt;--rsh&lt;/code&gt; option to make sure the helper
script gets executed.&lt;/p&gt;
&lt;h2 id=&quot;Issues&quot; name=&quot;Issues&quot;&gt;Issues&lt;/h2&gt;
&lt;p&gt;Unfortunately the local helper script does not detect that the transfer
has completed.  The remote helper script would finish correctly and
exit when the transfer is done.&lt;/p&gt;
&lt;p&gt;You can simply press &lt;code&gt;Ctrl+C&lt;/code&gt; to quit, or if you want to see any
summary stats, I would kill the running &lt;code&gt;cat&lt;/code&gt; command.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ bg
[2]+ ./send -avzr --stats SRC localhost:DST &amp;amp;
$ pidof cat
13143
$ kill 13143
$ Terminated

Number of files: 6 (reg: 5, dir: 1)
Number of created files: 0
Number of deleted files: 0
Number of regular files transferred: 0
Total file size: 5,869 bytes
Total transferred file size: 0 bytes
Literal data: 0 bytes
Matched data: 0 bytes
File list size: 0
File list generation time: 0.001 seconds
File list transfer time: 0.000 seconds
Total bytes sent: 202
Total bytes received: 17

sent 202 bytes  received 17 bytes  438.00 bytes/sec
total size is 5,869  speedup is 26.80
rsync error: syntax or usage error (code 1) at main.c(1189) [sender=3.1.3]
$&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>Resizing Virtual Disks with virsh</title>
<link href="https://www.0ink.net/posts/2019/2019-03-10-resizing-vdisks.html"></link>
<id>urn:uuid:9bf4bcdd-c7ac-af7e-96b8-1ceb59afdbaa</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[I am currently using libvirt for managing my VMs.  For virtual discs
I am using LVM2 volumes.  On a regular basis I need to resize
these virtual discs, but not that often that I can do this from
memory.  This is a short procedure to do this:
ls -l /dev/vgX/lvX      # note down the major/minor numbers for later
lvextend -L +50G /dev/vgX/lvX   # adding 50GB to this volume
...]]></summary>
<content type="html">&lt;p&gt;I am currently using &lt;code&gt;libvirt&lt;/code&gt; for managing my VMs.  For virtual discs
I am using &lt;code&gt;LVM2&lt;/code&gt; volumes.  On a regular basis I need to resize
these virtual discs, but not that often that I can do this from
memory.  This is a short procedure to do this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ls -l /dev/vgX/lvX      # note down the major/minor numbers for later
lvextend -L +50G /dev/vgX/lvX   # adding 50GB to this volume
cat /proc/partitions        # look up the size (in blocks) using major/minor numbers
virsh blockresize --path /dev/vgX/lvX --size SIZE_FROM_PROC_PARTITIONS vmname&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then on the running system you can do:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat /proc/partitions        # Make sure that size is right
xfs_growfs /mount/point     # On-line partition re-size&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>Z-Wave Associations with With Vera UI</title>
<link href="https://www.0ink.net/posts/2019/2019-03-01-z-wave-associations-with-vera.html"></link>
<id>urn:uuid:56e81e8f-984d-57ae-4637-1ef5c4de2cbf</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[I couldn't find any to the point documentation on how to do this,
so I am writing this.
The way I understand Z-Wave associations work is that once devices
are in the same Z-Wave network, a device can directly send
a command to another device without intervention of the Hub
or controller.
...]]></summary>
<content type="html">&lt;p&gt;I couldn&#039;t find any to the point documentation on how to do this,
so I am writing this.&lt;/p&gt;
&lt;p&gt;The way I understand Z-Wave associations work is that once devices
are in the same Z-Wave network, a device can directly send
a command to another device without intervention of the Hub
or controller.&lt;/p&gt;
&lt;p&gt;For this to really work the master device sending commands must
support this functionality.  This varies from device to device,
so you must look up the documentation of the master device and
find the supported associations groups.  In a nutshell, you look-up
what each association group is for, and then you add to that group
the slave devices that will receive the Z-wave commands from the
master.&lt;/p&gt;
&lt;p&gt;So for example, a &lt;code&gt;TKB-Home TZ55D&lt;/code&gt; Wall mounted dimmer/switch
with two buttons has the
following association groups:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Group 1: Control device using the left button.&lt;/li&gt;
&lt;li&gt;Group 2: Control device using the right button.&lt;/li&gt;
&lt;li&gt;Group 3: Control device using the right button after a double tap.&lt;/li&gt;
&lt;li&gt;Group 4: Control device so that it follows the switch state.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So this dimmer/switch has two buttons, the left button is used for
local control.  The right button can be used to control devices
associated to groups 2 and 3.&lt;/p&gt;
&lt;p&gt;In the Vera UI7 this is done as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Go to the &lt;code&gt;Devices&lt;/code&gt; list.&lt;/li&gt;
&lt;li&gt;Locate the &lt;em&gt;master&lt;/em&gt; device in the list and click it &lt;code&gt;settings&lt;/code&gt;
button.&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;Device Options&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Under &lt;em&gt;Associations&lt;/em&gt;, enter the &lt;code&gt;Group ID&lt;/code&gt; from the documentation
as explained above, and click &lt;code&gt;Add group&lt;/code&gt;.
You may need to click &lt;code&gt;Back&lt;/code&gt; and enter &lt;code&gt;Device Options&lt;/code&gt; again
for the group to be visible.&lt;/li&gt;
&lt;li&gt;Once the desired &lt;code&gt;Group ID&lt;/code&gt; is available, click on the &lt;code&gt;Set&lt;/code&gt;
button.&lt;/li&gt;
&lt;li&gt;Click the Z-Wave devices checkbox to add them to the group.
Leave the entry field next to the device is to enter sub-device
ids.  This is used for multichannel devices.  For example, an
RGBW controller may have multiple channels to control the
different color LEDs.  Check the &lt;em&gt;slave&lt;/em&gt; device documentation
for the valid sub-channel IDs to use.&lt;/li&gt;
&lt;/ol&gt;</content>
</entry>
<entry>
<title>Encrypting FileSystem in Void Linux</title>
<link href="https://www.0ink.net/posts/2019/2019-02-28-encrypting-fs-in-void.html"></link>
<id>urn:uuid:390b790d-9248-c91b-7603-568364aab380</id>
<updated>2025-01-01T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[The point of this recipe is to create a encrypted file sytem
so that when the disc is disposed, it does not need to be
securely erased.  This is particularly important for SSD devices
since because of block remapping (for wear levelling) data can't
be overwritten consistently.
The idea is that the boot/root filesystem containing the encryption
...]]></summary>
<content type="html">&lt;p&gt;The point of this recipe is to create a encrypted file sytem
so that when the disc is disposed, it does not need to be
securely erased.  This is particularly important for SSD devices
since because of block remapping (for wear levelling) data can&#039;t
be overwritten consistently.&lt;/p&gt;
&lt;p&gt;The idea is that the boot/root filesystem containing the encryption
keys are stored in a different device as the encrypted file system.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Generate a passphrase and save it a safe place for later.&lt;/p&gt;
&lt;h2 id=&quot;Create+block+devices&quot; name=&quot;Create+block+devices&quot;&gt;Create block devices&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;xbps-install -S lvm2 cryptsetup
cryptsetup luksFormat /dev/xda2
cryptsetup luksOpen /dev/xda2 crypt-pool
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alternate commands&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dd if=/dev/urandom of=/crypto_keyfile.bin bs=1024 count=4
chmod 000 /crypto_keyfile.bin
cryptsetup luksFormat /dev/xda2 /crypto_keyfile.bin
cryptsetup luksOpen --key-file=/crypto_keyfile.bin /dev/xda2 crypt-pool
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add &lt;code&gt;rd.luks.crypttab=1 rd.luks=1&lt;/code&gt; to the kernel command line.&lt;/p&gt;
&lt;h2 id=&quot;Create+a+decryption+key&quot; name=&quot;Create+a+decryption+key&quot;&gt;Create a decryption key&lt;/h2&gt;
&lt;p&gt;Create the key file in the unencrypted &lt;code&gt;/&lt;/code&gt; partition&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dd if=/dev/urandom of=/crypto_keyfile.bin bs=1024 count=4
chmod 000 /crypto_keyfile.bin
cryptsetup -v luksAddKey /dev/xda2 /crypto_keyfile.bin&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Look up the UUID&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;blkid /dev/xda2&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create entry in &lt;code&gt;/etc/crypttab&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;crypt-pool  UUID=xxxxxxxxxxxxxxxx /crypto_keyfile.bin luks&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you want to enable &lt;code&gt;discard&lt;/code&gt; option in flash sorage use this instead:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;crypt-pool  UUID=xxxxxxxxxxxxxxxx /crypto_keyfile.bin luks,discard&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create &lt;code&gt;/etc/dracut.conf.d/10-crypt.conf&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;install_items+=&quot;/etc/crypttab /crypto_keyfile.bin&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Update initrd:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;xbps-reconfigure -f linux4.19&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Update boot menu entries:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bash /boot/mkmenu.sh&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point it would be good to save:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/etc/crypttab&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/crypto_keyfile.bin&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Optionally, passphrase&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;Reboot and make sure that the block device gets created on start-up.&lt;/p&gt;
&lt;p&gt;Create your file-system and add it to &lt;code&gt;/etc/fstab&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vgcreate pool /dev/mapper/crypt-pool
lvcreate --name home0 -L 20G pool
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Re-using+an+existing+fs+in+a+new+OS+install&quot; name=&quot;Re-using+an+existing+fs+in+a+new+OS+install&quot;&gt;Re-using an existing fs in a new OS install&lt;/h2&gt;
&lt;p&gt;Usually this procedure would be used on a fresh install when the
root filesystem was destroyed.  It requires to have a backup of the
&lt;code&gt;/crypto_keyfile.bin&lt;/code&gt; and optionally the &lt;code&gt;/etc/crypttab&lt;/code&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Add &lt;code&gt;rd.luks.crypttab=1 rd.luks=1&lt;/code&gt; to the kernel command line.&lt;/li&gt;
&lt;li&gt;Restore the &lt;code&gt;/crypto_keyfile.bin&lt;/code&gt;.  Make sure it is in &lt;code&gt;/&lt;/code&gt; and
permissions are &lt;code&gt;chmod 000 /crypto_keyfile.bin&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If available, restore the &lt;code&gt;/etc/crypttab&lt;/code&gt; otherwise look up the
block device UUID and re-create the &lt;code&gt;/etc/crypttab&lt;/code&gt; entry:
&lt;ul&gt;
&lt;li&gt;Look-up the UUID:&lt;/li&gt;
&lt;li&gt;&lt;code&gt;blkid /dev/xda2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Add the entry in &lt;code&gt;/etc/crypttab&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;crypt-pool    UUID=xxxxxxxxxxxxxxxx /crypto_keyfile.bin luks&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Create &lt;code&gt;/etc/dracut.conf.d/10-crypt.conf&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;install_items+=&quot;/etc/crypttab /crypto_keyfile.bin&quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Update initrd:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;xbps-reconfigure -f linux4.19&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Update boot menu entries:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;bash /boot/mkmenu.sh&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Installing Void Linux</title>
<link href="https://www.0ink.net/posts/2019/2019-02-19-installing-void.html"></link>
<id>urn:uuid:a4665b65-2ddb-1a72-8951-d6994eef1d8c</id>
<updated>2025-07-09T00:00:00+02:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[I made the switch to void linux.  Except for compatibility
issues around glibc, it works quite well.  Most compatibility
I have worked around with a combination of Flatpaks, chroots and
namespaces.
The highlights of void linux:

...]]></summary>
<content type="html">&lt;p&gt;I made the switch to &lt;a href=&quot;https://voidlinux.org&quot; title=&quot;Void Linux&quot;&gt;void linux&lt;/a&gt;.  Except for compatibility
issues around &lt;code&gt;glibc&lt;/code&gt;, it works quite well.  Most compatibility
I have worked around with a combination of &lt;code&gt;Flatpak&lt;/code&gt;s, &lt;code&gt;chroot&lt;/code&gt;s and
&lt;code&gt;namespaces&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The highlights of &lt;a href=&quot;https://voidlinux.org&quot; title=&quot;Void Linux&quot;&gt;void linux&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;musl build - which is very lightweigth&lt;/li&gt;
&lt;li&gt;Does not depend on &lt;code&gt;systemd&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;a reasonable selection of software packages&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I have tweaked the installation on my computers to use UEFI and thus
I am using &lt;a href=&quot;http://www.rodsbooks.com/refind/&quot; title=&quot;rEFInd bootloader&quot;&gt;rEFInd&lt;/a&gt; instead of grub.  This is because it makes
doing bare metal backups and restore just a simple file copy.&lt;/p&gt;
&lt;p&gt;My installation process roughly follows the void linux
&lt;a href=&quot;https://wiki.voidlinux.org/Installation_on_UEFI,_via_chroot&quot; title=&quot;Install void linux on UEFI via chroot&quot;&gt;UEFI chroot install&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This process is implemented in a script and can be found here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/snippets/2019/void-installation/install.sh&quot;&gt;install.sh&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Script usage:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;    Usage: installer.sh _/dev/sdx_ _hostname_ [options]

    - _sdx_: Block device to install to or
      - --image=filepath[:size] to create a virtual disc image
      - --imgset=filebase[:size] to create a virtual filesystem image set
      - --dir=dirpath to create a directory
    - _hostname_: Hostname to use

    Options:
    - swap=kbs : swap size, defaults computed from /proc/meminfo, uses numfmt to parse values
    - glibc : Do a glibc install
    - noxwin : do not insall X11 related packages
    - nodesktop ; do not install desktop environment
    - desktop=mate : Install MATE dekstop environment
    - passwd=password : root password (prompt if not specified)
    - enc-passwd=encrypted : encrypted root password.
    - ovl=tar.gz : tarball containing additional files
    - post=script : run a post install script
    - pkgs=file : text file containing additional software to install
    - bios : create a BIOS boot system (needs syslinux)
    - cache=path : use the file path for download cache
    - xen : do some xen specific tweaks
    - xdm-candy : Enable xdm candy
    - noxdm : disable graphical login&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;Command+line+examples&quot; name=&quot;Command+line+examples&quot;&gt;Command line examples&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;sudo sh install.sh --dir=$HOME/vx9 vx9 swap=4G glibc passwd=1234567890 cache=$HOME/void-cache xen&lt;/li&gt;
&lt;li&gt;sudo sh install.sh --dir=$HOME/vx1 vx1 swap=4G glibc passwd=1234567890 cache=$HOME/void-cache xen&lt;/li&gt;
&lt;li&gt;sudo sh install.sh --dir=$HOME/vx11 vx11 swap=4G       passwd=1234567890 cache=$HOME/void-cache xen&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Initial+set-up&quot; name=&quot;Initial+set-up&quot;&gt;Initial set-up&lt;/h3&gt;
&lt;p&gt;Boot using the void live CD and partition the target disk:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cfdisk -z /dev/xda&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make sure you use &lt;code&gt;gpt&lt;/code&gt; label type (for UEFI boot).  I am creating
the following partitions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;800MB &lt;code&gt;EFI System&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;RAM Size x 1.5&lt;/em&gt; &lt;code&gt;Linux swap&lt;/code&gt;, Mainly used for Hibernate.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Rest of drive&lt;/em&gt; &lt;code&gt;Linux filesystem&lt;/code&gt;, Root file system&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When I first published this article back in 2019, I was using 500MB for
the EFI filesystem.  Now I am using 800MB.  This is because the size of the
Linux kernel including all the drivers have been growing over time.
With 800MB you can only have about one or two spare kernels.&lt;/p&gt;
&lt;p&gt;This is on a USB thumb drive.  The data I keep on an internal disk.&lt;/p&gt;
&lt;p&gt;Now we create the filesystems:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkfs.vfat -F 32 -n EFI /dev/xda1
mkswap -L swp0 /dev/xda2
mkfs.xfs -f -L voidlinux /dev/xda3&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&#039;re now ready to mount the volumes, making any necessary mount point directories along the way (the sequence is important, yes):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mount /dev/xda3 /mnt
mkdir /mnt/boot
mount /dev/xda1 /mnt/boot&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Installing+Void&quot; name=&quot;Installing+Void&quot;&gt;Installing Void&lt;/h3&gt;
&lt;p&gt;So we do a targeted install:&lt;/p&gt;
&lt;p&gt;For musl-libc&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;env XBPS_ARCH=x86_64-musl xbps-install -S :##     -R http://repo-default.voidlinux.org/current/musl :##     -r /mnt :##     base-system&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For glibc&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;env XBPS_ARCH=x86_64 xbps-install -S :##     -R http://repo-default.repo.voidlinux.org/current :##     -r /mnt :##     base-system&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But actually, for the package list I have been using these lists:&lt;/p&gt;
&lt;p&gt;base list:&lt;/p&gt;
&lt;script src=&quot;https://tortugalabs.github.io/embed-like-gist/embed.js?style=github&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showCopy=on&amp;amp;fetchFromJsDelivr=on&amp;amp;target=https://github.com/alejandroliu/0ink.net/blob/main/snippets/2019/void-installation/swlist.txt&quot;&gt;&lt;/script&gt;
&lt;p&gt;x-windows:&lt;/p&gt;
&lt;script src=&quot;https://tortugalabs.github.io/embed-like-gist/embed.js?style=github&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showCopy=on&amp;amp;fetchFromJsDelivr=on&amp;amp;target=https://github.com/alejandroliu/0ink.net/blob/main/snippets/2019/void-installation/swlist-xwin.txt?footer=minimal&quot;&gt;&lt;/script&gt;
&lt;h4 id=&quot;Software+selection+notes&quot; name=&quot;Software+selection+notes&quot;&gt;Software selection notes&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;For time synchronisation (ntp) we are choosing &lt;code&gt;chrony&lt;/code&gt; as it is
reputed to be more secure that &lt;code&gt;ntpd&lt;/code&gt; and more compliant than
&lt;code&gt;openntpd&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;We are using the default configuration, which should be OK.  Uses
&lt;code&gt;pool.ntp.org&lt;/code&gt; for the time server which would use a suitable
default.&lt;/li&gt;
&lt;li&gt;For &lt;code&gt;cron&lt;/code&gt; we are using &lt;code&gt;dcron&lt;/code&gt;.  It is full featured (i.e.
compatibnle with &lt;code&gt;cron&lt;/code&gt; and it can handle power-off situations,
while being the most light-weight option available.
See: &lt;a href=&quot;https://voidlinux.org/faq/#cron&quot;&gt;VoidLinux FAQ: Cron&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Includes &lt;code&gt;autofs&lt;/code&gt; and &lt;code&gt;nfs-utils&lt;/code&gt; for network filesystems and
automount support.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;nonfree+software+and+other+repositories&quot; name=&quot;nonfree+software+and+other+repositories&quot;&gt;nonfree software and other repositories&lt;/h3&gt;
&lt;p&gt;Additional repositories are available to support either
non-free software and in the case of glibc, multilib (32 bit)
binaries.&lt;/p&gt;
&lt;p&gt;To enable under the musl version:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;env XBPS_ARCH=&quot;$arch&quot; xbps-install -y -S -R &quot;$voidurl&quot; -r /mnt :##     void-repo-nonfree&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For glibc:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;env XBPS_ARCH=&quot;$arch&quot; xbps-install -y -S -R &quot;$voidurl&quot; -r /mnt :##     void-repo-nonfree void-repo-multilib void-repo-multilib-nonfree&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then you can install non-free software, like:&lt;/p&gt;
&lt;script src=&quot;https://tortugalabs.github.io/embed-like-gist/embed.js?style=github&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showCopy=on&amp;amp;fetchFromJsDelivr=on&amp;amp;target=https://github.com/alejandroliu/0ink.net/blob/main/snippets/2019/void-installation/swlist-nonfree.txt&quot;&gt;&lt;/script&gt;
&lt;h3 id=&quot;Enter+the+void+chroot&quot; name=&quot;Enter+the+void+chroot&quot;&gt;Enter the void chroot&lt;/h3&gt;
&lt;p&gt;Upon completion of the install, we set up our chroot jail, and chroot into our mounted filesystem:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mount -t proc proc /mnt/proc
mount -t sysfs sys /mnt/sys
mount -o bind /dev /mnt/dev
mount -t devpts pts /mnt/dev/pts
cp -L /etc/resolv.conf /mnt/etc/
chroot /mnt bash -il&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In order to verify our install, we can have a look at the directory structure:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt; ls -la&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output should look something akin to the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;total 12
drwxr-xr-x 16 root root 4096 Jan 17 15:27 .
drwxr-xr-x  3 root root 4096 Jan 17 15:16 ..
lrwxrwxrwx  1 root root    7 Jan 17 15:26 bin -&amp;gt; usr/bin
drwxr-xr-x  4 root root  127 Jan 17 15:37 boot
drwxr-xr-x  2 root root   17 Jan 17 15:26 dev
drwxr-xr-x 26 root root 4096 Jan 17 15:27 etc
drwxr-xr-x  2 root root    6 Jan 17 15:26 home
lrwxrwxrwx  1 root root    7 Jan 17 15:26 lib -&amp;gt; usr/lib
lrwxrwxrwx  1 root root    9 Jan 17 15:26 lib32 -&amp;gt; usr/lib32
lrwxrwxrwx  1 root root    7 Jan 17 15:26 lib64 -&amp;gt; usr/lib
drwxr-xr-x  2 root root    6 Jan 17 15:26 media
drwxr-xr-x  2 root root    6 Jan 17 15:26 mnt
drwxr-xr-x  2 root root    6 Jan 17 15:26 opt
drwxr-xr-x  2 root root    6 Jan 17 15:26 proc
drwxr-x---  2 root root   26 Jan 17 15:39 root
drwxr-xr-x  3 root root   17 Jan 17 15:26 run
lrwxrwxrwx  1 root root    8 Jan 17 15:26 sbin -&amp;gt; usr/sbin
drwxr-xr-x  2 root root    6 Jan 17 15:26 sys
drwxrwxrwt  2 root root    6 Jan 17 15:15 tmp
drwxr-xr-x 11 root root  123 Jan 17 15:26 usr
drwxr-xr-x 11 root root  150 Jan 17 15:26 var&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While chrooted, we create the password for the root user, and set root access permissions:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt; passwd root
 chown root:root /
 chmod 755 /&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since I am a &lt;code&gt;bash&lt;/code&gt; convert, I would do this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt; xbps-alternatives --set bash&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create the &lt;code&gt;hostname&lt;/code&gt; for the new install:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;echo &amp;lt;HOSTNAME&amp;gt; &amp;gt; /etc/hostname&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Edit our &lt;code&gt;/etc/rc.conf&lt;/code&gt; file, like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;HOSTNAME=&quot;&amp;lt;HOSTNAME&amp;gt;&quot;

# Set RTC to UTC or localtime.
HARDWARECLOCK=&quot;UTC&quot;

# Set timezone, availables timezones at /usr/share/zoneinfo.
TIMEZONE=&quot;Europe/Amsterdam&quot;

# Keymap to load, see loadkeys(8).
KEYMAP=&quot;us-acentos&quot;

# Console font to load, see setfont(8).
#FONT=&quot;lat9w-16&quot;

# Console map to load, see setfont(8).
#FONT_MAP=

# Font unimap to load, see setfont(8).
#FONT_UNIMAP=

# Kernel modules to load, delimited by blanks.
#MODULES=&quot;&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also, modify the &lt;code&gt;/etc/fstab&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;#
# See fstab(5).

# &amp;lt;file system&amp;gt; &amp;lt;dir&amp;gt;   &amp;lt;type&amp;gt;  &amp;lt;options&amp;gt;       &amp;lt;dump&amp;gt;  &amp;lt;pass&amp;gt;
tmpfs       /tmp    tmpfs   defaults,nosuid,nodev   0       0
LABEL=EFI   /boot   vfat    rw,fmask=0133,dmask=0022,noatime,discard  0 2
LABEL=voidlinux /   xfs rw,relatime,discard 0 1
LABEL=swp0  swap    swap    defaults        0 0&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For a removable drive I include the line:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;LABEL=volume    /media/blahblah xfs rw,relatime,nofail 0 0&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The important setting here is &lt;strong&gt;nofail&lt;/strong&gt;.  When the drive is
available it gets mounted.  If not, the &lt;strong&gt;nofail&lt;/strong&gt; prevents
this to cause the boot sequence to stop.&lt;/p&gt;
&lt;p&gt;If using &lt;code&gt;glibc&lt;/code&gt; you can modify &lt;code&gt;/etc/default/libc-locales&lt;/code&gt; and
uncomment:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;en_US.UTF-8 UTF-8&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Or whatever locale you want to use.  And run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt; xbps-reconfigure -f glibc-locales&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Set-up+UEFI+boot&quot; name=&quot;Set-up+UEFI+boot&quot;&gt;Set-up UEFI boot&lt;/h3&gt;
&lt;p&gt;Download the &lt;a href=&quot;http://www.rodsbooks.com/refind/&quot; title=&quot;rEFInd bootloader&quot;&gt;rEFInd&lt;/a&gt; zip binary from:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.rodsbooks.com/refind/getting.html&quot; title=&quot;rEFInd download page&quot;&gt;rEFInd download&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Set-up the boot partition:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir /boot/EFI
mkdir /boot/EFI/BOOT&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Copy from the &lt;code&gt;zip file&lt;/code&gt; the file &lt;code&gt;refind-bin-{version}/refind/refind_x64.efi&lt;/code&gt; to
&lt;code&gt;/boot/EFI/BOOT/BOOTX64.EFI&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The version I am using right now can be found here: &lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/snippets/2019/void-installation/BOOTX64.EFI&quot;&gt;v0.14.2 BOOTX64.EFI&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Create kernel options files &lt;code&gt;/boot/cmdline&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;root=LABEL=voidlinux ro quiet
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Depending of your hardware you may add additional options.  For example,
at one time I hadd to use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;intel_iommu=igfx_off&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;To work around some strange bug.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;i915.enable_ips=0&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;fixes a power saving mode problem on 4.1-rc6+&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Create the following script as &lt;code&gt;/boot/mkmenu.sh&lt;/code&gt;&lt;/p&gt;
&lt;script src=&quot;https://tortugalabs.github.io/embed-like-gist/embed.js?style=github&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showCopy=on&amp;amp;fetchFromJsDelivr=on&amp;amp;target=https://github.com/alejandroliu/0ink.net/blob/main/snippets/2019/void-installation/mkmenu.sh&quot;&gt;&lt;/script&gt;
&lt;p&gt;Add the following scripts to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/etc/kernel.d/post-install/99-refind&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/etc/kernel.d/post-remove/99-refind&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;script src=&quot;https://tortugalabs.github.io/embed-like-gist/embed.js?style=github&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showCopy=on&amp;amp;fetchFromJsDelivr=on&amp;amp;target=https://github.com/alejandroliu/0ink.net/blob/main/snippets/2019/void-installation/hook.sh&quot;&gt;&lt;/script&gt;
&lt;p&gt;Make sure they are executable.  This is supposed to re-create
menu entries whenever the kernel gets upgraded.&lt;/p&gt;
&lt;p&gt;We need to have a look at &lt;code&gt;/lib/modules&lt;/code&gt; to get our Linux kernel version&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ls -la /lib/modules&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which should return something akin to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;drwxr-xr-x  3 root root   21 Jan 31 15:22 .
drwxr-xr-x 23 root root 8192 Jan 31 15:22 ..
drwxr-xr-x  3 root root 4096 Jan 31 15:22 5.2.13_1&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And this script to create boot files:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;xbps-reconfigure -f linux5.2&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you need to manually prepare boot files:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# update dracut
dracut --force --kver 4.19.4_1
# update refind menu
bash /boot/mkmenu.sh&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We are now ready to boot into &lt;a href=&quot;https://voidlinux.org&quot; title=&quot;Void Linux&quot;&gt;Void&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;exit
umount -R /mnt
reboot&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Post+install&quot; name=&quot;Post+install&quot;&gt;Post install&lt;/h3&gt;
&lt;p&gt;After the first boot, we need to activate services:&lt;/p&gt;
&lt;p&gt;Command line set-up:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ln -s /etc/sv/dhcpcd /var/service
ln -s /etc/sv/sshd /var/service
ln -s /etc/sv/{acpid,chronyd,crond,uuidd,statd,rcpbind,autofs,socklog-unix,nanoklogd} /var/service&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;
Creating new users:

```bash
useradd -m -s /bin/bash -U -G wheel,users,audio,video,cdrom,input newuser
passwd newuser&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note: The &lt;code&gt;wheel&lt;/code&gt; user group allows the user to escalate to root.&lt;/p&gt;
&lt;p&gt;Configure sudo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;visudo&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Uncomment:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# %wheel ALL=(ALL) ALL&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Configure+keyboard&quot; name=&quot;Configure+keyboard&quot;&gt;Configure keyboard&lt;/h3&gt;
&lt;p&gt;Create configuration file: &lt;code&gt;/etc/X11/xorg.conf.d/30-keyboard.conf&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xorg.conf&quot;&gt;Section &quot;InputClass&quot;
    Identifier &quot;keyboard-all&quot;
    Option &quot;XkbLayout&quot; &quot;us&quot;
    # Option &quot;XkbModel&quot; &quot;pc105&quot;
    # Option &quot;XkbVariant&quot; &quot;altgr-intl&quot;
    Option &quot;XkbVariant&quot; &quot;intl&quot;
    # MatchIsKeyboard &quot;on&quot;
EndSection&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This makes the &lt;code&gt;intl&lt;/code&gt; for the &lt;code&gt;XkbVariant&lt;/code&gt; the system-wide default.&lt;/p&gt;
&lt;p&gt;Since, as a programmer I prefer the &lt;code&gt;altgr-intl&lt;/code&gt; variant, then I
run this in my de desktop environment startup to override the
default:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;setxkbmap -rules evdev -model evdev -layout us -variant altgr-intl&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I don&#039;t normally use a display manager.  To start the GUI enviornment,
you can add a file in &lt;code&gt;/etc/profile.d&lt;/code&gt; to start X
at login if on tty1.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/snippets/2019/void-installation/Xsession&quot;&gt;session&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alejandroliu/0ink.net/blob/main/snippets/2019/void-installation/noxdm/zzdm.sh&quot;&gt;zzdm.sh&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I am using the &lt;code&gt;session&lt;/code&gt; script, which is a modified version of
an earlier &lt;code&gt;Xsession&lt;/code&gt; script that I was using for &lt;code&gt;xdm&lt;/code&gt; to
launch a desktop session.&lt;/p&gt;
&lt;p&gt;The script &lt;code&gt;zzdm.sh&lt;/code&gt; is used to &lt;code&gt;startx&lt;/code&gt; on login.&lt;/p&gt;
&lt;h3 id=&quot;Tweaks+and+Bug-fixes&quot; name=&quot;Tweaks+and+Bug-fixes&quot;&gt;Tweaks and Bug-fixes&lt;/h3&gt;
&lt;h4 id=&quot;%2Fetc%2Fmachine-id+or+%2Fvar%2Flib%2Fdbus%2Fmachine-id&quot; name=&quot;%2Fetc%2Fmachine-id+or+%2Fvar%2Flib%2Fdbus%2Fmachine-id&quot;&gt;/etc/machine-id or /var/lib/dbus/machine-id&lt;/h4&gt;
&lt;p&gt;Because we don&#039;t use &lt;code&gt;systemd&lt;/code&gt;, we need to create &lt;code&gt;/etc/machine-id&lt;/code&gt;
and &lt;code&gt;/var/lib/dbus/machine-id&lt;/code&gt;.
manually.  This is only needed for desktop systems.&lt;/p&gt;
&lt;p&gt;See [this article][machineid] for more
info.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  dbus-uuidgen | tee /etc/machine-id /var/lib/dbus/machine-id&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;power+button+handling&quot; name=&quot;power+button+handling&quot;&gt;power button handling&lt;/h4&gt;
&lt;p&gt;This patch prevents the &lt;code&gt;/etc/acpi/handler.sh&lt;/code&gt; to handle the power button
instead, letting the Desktop Environment handle the event.&lt;/p&gt;
&lt;p&gt;It does it by checking if a X session is running.  In the
&lt;code&gt;/etc/rc.local&lt;/code&gt; script, we create a file called
&lt;code&gt;/run/xsession.pid&lt;/code&gt; which is made writeable by all.
The system is configured so that &lt;code&gt;xdm&lt;/code&gt; or &lt;code&gt;/etc/profile/zzdm.sh&lt;/code&gt;
(when login as normal user on &lt;code&gt;tty1&lt;/code&gt;) will start an X session
and will use the scripts &lt;code&gt;/etc/X11/xinit/session&lt;/code&gt; or
&lt;code&gt;/etc/X11/xdm/Xsession&lt;/code&gt; to start the session.
From these scripts, the current X session information is saved
to &lt;code&gt;/run/xsession.pid&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;When &lt;code&gt;/etc/acpi/handler.sh&lt;/code&gt; starts, it will check
&lt;code&gt;/run/session.pid&lt;/code&gt; if it contains a running session.  It will
also check if a  Desktop Environment power manager
(in this case &lt;code&gt;mate-power-manager&lt;/code&gt;) is running.  If it is, then
it will exit.&lt;/p&gt;
&lt;script src=&quot;https://tortugalabs.github.io/embed-like-gist/embed.js?style=github&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showCopy=on&amp;amp;fetchFromJsDelivr=on&amp;amp;target=https://github.com/alejandroliu/0ink.net/blob/main/snippets/2019/void-installation/acpi-handler.patch&quot;&gt;&lt;/script&gt;
&lt;h4 id=&quot;xen+tweaks&quot; name=&quot;xen+tweaks&quot;&gt;xen tweaks&lt;/h4&gt;
&lt;p&gt;For xen we need to make some adjustments...&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Tweak block device references.
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/etc/fstab&lt;/code&gt; : mount xvda and other devices&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/boot/cmdline&lt;/code&gt; : get the right xvda root device&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Enable disable services
&lt;ul&gt;
&lt;li&gt;Disable: &lt;code&gt;slim&lt;/code&gt;, &lt;code&gt;agetty-ttyX&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Enable: &lt;code&gt;agetty-hvc0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Decide if you want to use &lt;code&gt;NetworkManager&lt;/code&gt; or &lt;code&gt;dhcpcd&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Normally, I would create a tarball image to transfer over, in order
for the image to work properly you need to save &lt;code&gt;capabilities&lt;/code&gt;.&lt;/p&gt;
&lt;hr /&gt;</content>
</entry>
<entry>
<title>My Linux Keyboard Shortcuts</title>
<link href="https://www.0ink.net/posts/2019/2019-02-11-my-linux-shortcuts.html"></link>
<id>urn:uuid:b7d00dbb-2915-3f4c-f285-e68d5ad4cf6a</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[In general we try to be similar to MS-Windows shortcuts.
Default bindings (in MATE)



Key
...]]></summary>
<content type="html">&lt;p&gt;In general we try to be similar to MS-Windows shortcuts.&lt;/p&gt;
&lt;p&gt;Default bindings (in MATE)&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Alt + F4&lt;/td&gt;
&lt;td&gt;Close the active item, or exit the active program&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alt + Tab&lt;/td&gt;
&lt;td&gt;Switch between open items&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ctrl + Alt + Tab&lt;/td&gt;
&lt;td&gt;Use the arrow keys to switch between open items&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alt + Esc&lt;/td&gt;
&lt;td&gt;Cycle through items in the order in which they were opened&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alt + Spacebar&lt;/td&gt;
&lt;td&gt;Open the shortcut menu for the active window&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Custom bindings (Window Manager)&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ctrl + Esc&lt;/td&gt;
&lt;td&gt;Open the Start menu&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SuperKey + Up arrow&lt;/td&gt;
&lt;td&gt;Toggle Maximize the window.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SuperKey + Down arrow&lt;/td&gt;
&lt;td&gt;Remove current app from screen or minimize the desktop window.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SuperKey + Left arrow&lt;/td&gt;
&lt;td&gt;Maximize the app or desktop window to the left side of the screen.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SuperKey + Right arrow&lt;/td&gt;
&lt;td&gt;Maximize the app or desktop window to the right side of the screen.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SuperKey + D&lt;/td&gt;
&lt;td&gt;Display or hide the desktop.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This is configured using this script: &lt;a href=&quot;https://github.com/TortugaLabs/void-utils/blob/master/keys/keybindings.sh&quot;&gt;keybindings.sh&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Xbindkeys&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ctrl + Shift + Esc&lt;/td&gt;
&lt;td&gt;Open Task Manager&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SuperKey + L&lt;/td&gt;
&lt;td&gt;Lock your PC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SuperKey + E&lt;/td&gt;
&lt;td&gt;Open File Manager.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SuperKey + F&lt;/td&gt;
&lt;td&gt;Open search.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SuperKey + R&lt;/td&gt;
&lt;td&gt;Open the Run dialog box.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SuperKey + KP_Mult&lt;/td&gt;
&lt;td&gt;Volume Up&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SuperKey + KP_Minus&lt;/td&gt;
&lt;td&gt;Volume Down&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SuperKey + KP_Div&lt;/td&gt;
&lt;td&gt;Mute Toggle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SukerKey + KP_Add&lt;/td&gt;
&lt;td&gt;Switch PA output&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This makes use of this &lt;a href=&quot;https://github.com/TortugaLabs/void-utils/blob/master/keys/xbindkeysrc&quot;&gt;xbindkeysrc&lt;/a&gt; file.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TODO:&lt;/strong&gt; SuperKey + V | Open the clipboard. &lt;/p&gt;</content>
</entry>
<entry>
<title>Global Windows Keyboard Shorcuts</title>
<link href="https://www.0ink.net/posts/2019/2019-02-10-windows-shortcuts.html"></link>
<id>urn:uuid:9e614134-86e7-44c0-5f92-e94aef8aff89</id>
<updated>2022-03-03T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Common Window Management Shortcuts



Key
Action
...]]></summary>
<content type="html">&lt;p&gt;Common Window Management Shortcuts&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Alt + F4&lt;/td&gt;
&lt;td&gt;Close the active item, or exit the active program&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alt + Tab&lt;/td&gt;
&lt;td&gt;Switch between open items&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ctrl + Alt + Tab&lt;/td&gt;
&lt;td&gt;Use the arrow keys to switch between open items&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alt + Esc&lt;/td&gt;
&lt;td&gt;Cycle through items in the order in which they were opened&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ctrl + Esc&lt;/td&gt;
&lt;td&gt;Open the Start menu&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alt + Spacebar&lt;/td&gt;
&lt;td&gt;Open the shortcut menu for the active window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ctrl + Shift + Esc&lt;/td&gt;
&lt;td&gt;Open Task Manager&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WinKey&lt;/td&gt;
&lt;td&gt;Open or Close Start button&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WinKey + D&lt;/td&gt;
&lt;td&gt;Display or hide the desktop.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WinKey + E&lt;/td&gt;
&lt;td&gt;Open File Explorer.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WinKey + F&lt;/td&gt;
&lt;td&gt;Open File Explorer Search (Find).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WinKey + L&lt;/td&gt;
&lt;td&gt;Lock your PC or switch accounts.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WinKey + M&lt;/td&gt;
&lt;td&gt;Minimize all windows.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WinKey + Shift + M&lt;/td&gt;
&lt;td&gt;Restore minimized windows on the desktop.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WinKey + R&lt;/td&gt;
&lt;td&gt;Open the Run dialog box.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WinKey + V&lt;/td&gt;
&lt;td&gt;Open the clipboard.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WinKey + Up arrow&lt;/td&gt;
&lt;td&gt;Maximize the window.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WinKey + Down arrow&lt;/td&gt;
&lt;td&gt;Remove current app from screen or minimize the desktop window.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WinKey + Left arrow&lt;/td&gt;
&lt;td&gt;Maximize the app or desktop window to the left side of the screen.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WinKey + Right arrow&lt;/td&gt;
&lt;td&gt;Maximize the app or desktop window to the right side of the screen.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WinKey + Home&lt;/td&gt;
&lt;td&gt;Minimize all except the active desktop window (restores all windows on second stroke).&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Additional Window Management Shortcuts&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;WinKey + Tab&lt;/td&gt;
&lt;td&gt;Cycle through programs on the taskbar by using Aero Flip 3-D&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ctrl+WinKey + Tab&lt;/td&gt;
&lt;td&gt;Use the arrow keys to cycle through programs on the taskbar by using Aero Flip 3-D&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Left Alt + Shift&lt;/td&gt;
&lt;td&gt;Switch the input language when multiple input languages are enabled&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Rare Window Management Shortcuts&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ctrl+Shift&lt;/td&gt;
&lt;td&gt;Switch the keyboard layout when multiple keyboard layouts are enabled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Right or Left Ctrl + Shift&lt;/td&gt;
&lt;td&gt;Change the reading direction of text in right-to-left reading languages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WinKey + B&lt;/td&gt;
&lt;td&gt;Set focus in the notification area.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WinKey + P&lt;/td&gt;
&lt;td&gt;Choose a presentation display mode.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WinKey + T&lt;/td&gt;
&lt;td&gt;Cycle through apps on the taskbar.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WinKey + U&lt;/td&gt;
&lt;td&gt;Open Ease of Access Center.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WinKey + Pause&lt;/td&gt;
&lt;td&gt;Display the System Properties dialog box.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WinKey + Ctrl + Enter&lt;/td&gt;
&lt;td&gt;Turn on Narrator.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WinKey + Plus (+)&lt;/td&gt;
&lt;td&gt;Open Magnifier.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;CUA Shortcuts&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;F1&lt;/td&gt;
&lt;td&gt;Display Help&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;F2&lt;/td&gt;
&lt;td&gt;Rename the selected item&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;F3&lt;/td&gt;
&lt;td&gt;Search&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ctrl + F4&lt;/td&gt;
&lt;td&gt;Close the active document (in programs that allow you to have multiple documents open simultaneously)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;F5 (or Ctrl + R)&lt;/td&gt;
&lt;td&gt;Refresh the active window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;F6&lt;/td&gt;
&lt;td&gt;Cycle through screen elements in a window or on the desktop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;F10&lt;/td&gt;
&lt;td&gt;Activate the menu bar in the active program&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shift + F10&lt;/td&gt;
&lt;td&gt;Display the shortcut menu for the selected item&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Esc&lt;/td&gt;
&lt;td&gt;Cancel the current task&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delete (or Ctrl + D)&lt;/td&gt;
&lt;td&gt;Delete current item&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shift + Delete&lt;/td&gt;
&lt;td&gt;Delete current item (without undo?)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alt + Enter&lt;/td&gt;
&lt;td&gt;Display properties for the selected item&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ctrl + C (or Ctrl + Insert)&lt;/td&gt;
&lt;td&gt;Copy the selected item&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ctrl + X&lt;/td&gt;
&lt;td&gt;Cut the selected item&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ctrl + V (or Shift + Insert)&lt;/td&gt;
&lt;td&gt;Paste the selected item&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ctrl + Z&lt;/td&gt;
&lt;td&gt;Undo an action&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ctrl + Y&lt;/td&gt;
&lt;td&gt;Redo an action&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ctrl + A&lt;/td&gt;
&lt;td&gt;Select all items in a document or window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ctrl + Mouse scroll wheel&lt;/td&gt;
&lt;td&gt;Change zoom factor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alt + underlined letter&lt;/td&gt;
&lt;td&gt;Display the corresponding menu&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alt + underlined letter&lt;/td&gt;
&lt;td&gt;Perform the menu command (or other underlined command)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ctrl + Right Arrow&lt;/td&gt;
&lt;td&gt;Move the cursor to the beginning of the next word&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ctrl + Left Arrow&lt;/td&gt;
&lt;td&gt;Move the cursor to the beginning of the previous word&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ctrl + Down Arrow&lt;/td&gt;
&lt;td&gt;Move the cursor to the beginning of the next paragraph&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ctrl + Up Arrow&lt;/td&gt;
&lt;td&gt;Move the cursor to the beginning of the previous paragraph&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ctrl + Shift with an arrow key&lt;/td&gt;
&lt;td&gt;Select a block of text&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;</content>
</entry>
<entry>
<title>Third Party SimpleNote clients</title>
<link href="https://www.0ink.net/posts/2019/2019-01-08-simplenote-clients.html"></link>
<id>urn:uuid:ff2fdbc9-c5df-c664-386b-09d336fdc114</id>
<updated>2024-11-22T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[An inventory of simplenote clients:
SimpleNote clients:

sncli
nvpy
notestack
...]]></summary>
<content type="html">&lt;p&gt;An inventory of simplenote clients:&lt;/p&gt;
&lt;p&gt;SimpleNote clients:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/insanum/sncli&quot;&gt;sncli&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/cpbotha/nvpy&quot;&gt;nvpy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/brittohalloran/notestack&quot;&gt;notestack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/dotemacs/simplenote.el&quot;&gt;simplenote.el&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/carlo/simplenote-js&quot;&gt;simplenote-js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/simplenote-sync&quot;&gt;simplenote-sync&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/simplenote&quot;&gt;simplenote pkg&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Current plan is to use a &lt;code&gt;webapp&lt;/code&gt; wrapper for the actual
simplenote web site.&lt;/p&gt;</content>
</entry>
<entry>
<title>Docker on Alpine Linux</title>
<link href="https://www.0ink.net/posts/2018/2018-09-22-docker-notes.html"></link>
<id>urn:uuid:0087de2f-0f7d-c7f3-fc5a-3f83b05eb493</id>
<updated>2022-01-04T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Alpine Linux Quick installation
See wiki  For Alpine Linux &gt; 3.8

Un-comment community repo from /etc/apk/repositories
apk add docker
rc-update add docker boot
...]]></summary>
<content type="html">&lt;p&gt;Alpine Linux Quick installation&lt;/p&gt;
&lt;p&gt;See &lt;a href=&quot;https://wiki.alpinelinux.org/wiki/Docker&quot;&gt;wiki&lt;/a&gt;  For Alpine Linux &amp;gt; 3.8&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Un-comment community repo from &lt;code&gt;/etc/apk/repositories&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;apk add docker&lt;/li&gt;
&lt;li&gt;rc-update add docker boot&lt;/li&gt;
&lt;li&gt;service docker start&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Optional: (docker compose)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apk add docker-compose&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;Note 2021-03-21: When I tested this, the &lt;code&gt;daemon.json&lt;/code&gt; did not
work!  Your mileage may vary.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Recommended for user namespace isolation (not sure if this works)&lt;/p&gt;
&lt;p&gt;Also is good to use data mode (persistent /var) as most docker data is stored there.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;adduser -SDHs /sbin/nologin dockremap
addgroup -S dockremap
echo dockremap:100000:65535 | tee /etc/subuid
echo dockremap:100000:65535 | tee /etc/subgid&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In &lt;code&gt;/etc/docker/daemon.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
        &quot;userns-remap&quot;: &quot;dockremap&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For more info &lt;a href=&quot;https://docs.docker.com/engine/reference/commandline/dockerd/#daemon-configuration-file&quot;&gt;docker docs&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;Test+docker%3A&quot; name=&quot;Test+docker%3A&quot;&gt;Test docker:&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;docker version&lt;/li&gt;
&lt;li&gt;docker info&lt;/li&gt;
&lt;li&gt;docker run hello-world&lt;/li&gt;
&lt;li&gt;docker image ls&lt;/li&gt;
&lt;li&gt;docker container ls&lt;/li&gt;
&lt;li&gt;docker container ls --all&lt;/li&gt;
&lt;li&gt;docker container ls --aq&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;Mounting+NFS&quot; name=&quot;Mounting+NFS&quot;&gt;Mounting NFS&lt;/h3&gt;
&lt;p&gt;From docker 17.06, you can mount NFS shares to the container directly when you run it, without the need of extra capabilities&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker run --mount &#039;type=volume,src=VOL_NAME,volume-driver=local,dst=/LOCAL-MNT,volume-opt=type=nfs,volume-opt=device=:/NFS-SHARE,&quot;volume-opt=o=addr=NFS-SERVER,vers=4,hard,timeo=600,rsize=1048576,wsize=1048576,retrans=2&quot;&#039; -d -it --name mycontainer ubuntu&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Useful+options+for+docker&quot; name=&quot;Useful+options+for+docker&quot;&gt;Useful options for docker&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;docker run -d&lt;/code&gt; : Run as a daemon (runs in the background).&lt;/li&gt;
&lt;li&gt;NFS mounting (pre 17.06)
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;you@host &amp;gt; mount server:/dir /path/to/mount/point&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;you@host &amp;gt; docker run -v /path/to/mount/point:/path/to/mount/point&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docker run -p 4000:80&lt;/code&gt; : Forward port 4000 to 80.
So host listens on port 4000 and everything is forwarded to port 80 on the container.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Alpine+Linux+relocating+%2Fvar%2Flib%2Fdocker&quot; name=&quot;Alpine+Linux+relocating+%2Fvar%2Flib%2Fdocker&quot;&gt;Alpine Linux relocating /var/lib/docker&lt;/h3&gt;
&lt;p&gt;In the file &lt;code&gt;/etc/conf.d/docker&lt;/code&gt; you can add additional command line
options in:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;DOCKER_OPTS&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;In particular you can use the &lt;code&gt;-g&lt;/code&gt; option.&lt;/p&gt;
&lt;p&gt;See &lt;a href=&quot;https://linuxconfig.org/how-to-move-docker-s-default-var-lib-docker-to-another-directory-on-ubuntu-debian-linux&quot;&gt;linuxconfig.org article&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;Make+your+own+docker+image%2C+quick+example&quot; name=&quot;Make+your+own+docker+image%2C+quick+example&quot;&gt;Make your own docker image, quick example&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.docker.com/get-started/part2/#run-the-app&quot;&gt;Run the app&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Making+changes+to+an+existing+image&quot; name=&quot;Making+changes+to+an+existing+image&quot;&gt;Making changes to an existing image&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;docker run -i -t [--name guest] image_name /bin/bash|/bin/sh&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;... make changes to it ...&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docker stop name|container_id&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;`docker commit -m &#039;change name&#039; -a &#039;A N Other&#039; container_id image_name
&lt;ul&gt;
&lt;li&gt;container id: &lt;code&gt;$(docker ps -l -q)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docker rm guest|container_id&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Better+way+to+create+container+images%3A&quot; name=&quot;Better+way+to+create+container+images%3A&quot;&gt;Better way to create container images:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://serversforhackers.com/c/updating-containers&quot;&gt;Updating containers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Just+use+them...&quot; name=&quot;Just+use+them...&quot;&gt;Just use them...&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/maxexcloo/Docker&quot;&gt;docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.linuxserver.io/&quot;&gt;linuxerver&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Alpine on OTC</title>
<link href="https://www.0ink.net/posts/2018/2018-08-29-alpine-on-otc.html"></link>
<id>urn:uuid:3da212f0-a1a8-9a38-a9c1-73c18f69bde8</id>
<updated>2022-01-04T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[These are just random thoughts nothing really was implemented.
Alpine Linux image


preparation: jq and other deps to /apks/x86_64

...]]></summary>
<content type="html">&lt;p&gt;These are just random thoughts nothing really was implemented.&lt;/p&gt;
&lt;p&gt;Alpine Linux image&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;preparation: jq and other deps to &lt;code&gt;/apks/x86_64&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;/etc/local.d/&lt;/code&gt;
&lt;code&gt;cloud-init-lite&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;0&quot;&gt;
&lt;li&gt;if &lt;code&gt;/etc/network/intefaces&lt;/code&gt; exists we abort&lt;/li&gt;
&lt;li&gt;&lt;code&gt;apk add --force-non-repository /path oniguruma,jq&lt;/code&gt; .. restore &lt;code&gt;/etc/apk/world&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;udhcpc -b -p /var/run/udhcpc.eth0.pid -i eth0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;install &lt;code&gt;openssh&lt;/code&gt; and start it&lt;/li&gt;
&lt;li&gt;&lt;code&gt;wget&lt;/code&gt; meta data and create &lt;code&gt;/root/.ssh/authorized_keys&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;wget -O- http://169.254.169.254/openstack/YYYY-MM-DD/meta_data.json&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Windows Account Lockouts</title>
<link href="https://www.0ink.net/posts/2018/2018-07-19-locking-account.html"></link>
<id>urn:uuid:43643bfd-1a8a-5714-3618-56de1349deff</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[To prevent windows lockouts the following can be done:

Delete Internet Explorer browsing history
Run the following:

Open Start --&gt; Search filed--&gt; Type in Run --&gt; rundll32.exe keymgr.dll, KRShowKeyMgr --&gt; Delete
...]]></summary>
<content type="html">&lt;p&gt;To prevent windows lockouts the following can be done:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Delete Internet Explorer browsing history&lt;/li&gt;
&lt;li&gt;Run the following:
&lt;ul&gt;
&lt;li&gt;Open Start --&amp;gt; Search filed--&amp;gt; Type in Run --&amp;gt; rundll32.exe keymgr.dll, KRShowKeyMgr --&amp;gt; Delete&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Disconnect network shares&lt;/li&gt;
&lt;li&gt;Change password&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Skipping grep when using AWK</title>
<link href="https://www.0ink.net/posts/2018/2018-07-11-awk-vs-grep.html"></link>
<id>urn:uuid:66d04e63-394e-86d4-18df-2b95789d29ce</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Over the years, We've seen many people use this pattern (filter-map):
$ [data is generated] | grep something | awk '{print $2}'
but it can be shortened to:
$ [data is generated] | awk '/something/ {print $2}'
You (probably) don't need grep
Following this logic, you can replace a simple grep with:
...]]></summary>
<content type="html">&lt;p&gt;Over the years, We&#039;ve seen many people use this pattern (filter-map):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ [data is generated] | grep something | awk &#039;{print $2}&#039;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;but it can be shortened to:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ [data is generated] | awk &#039;/something/ {print $2}&#039;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;You+%28probably%29+don%27t+need+grep&quot; name=&quot;You+%28probably%29+don%27t+need+grep&quot;&gt;You (probably) don&#039;t need grep&lt;/h2&gt;
&lt;p&gt;Following this logic, you can replace a simple grep with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ [data is generated] | awk &#039;/something/&#039;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will &lt;em&gt;implicitly&lt;/em&gt; print lines that match the regular expression.&lt;/p&gt;
&lt;p&gt;If you feel lost, Here are a series of posts about awk for you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.jpalardy.com/posts/why-learn-awk/&quot;&gt;Why Learn AWK&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.jpalardy.com/posts/awk-tutorial-part-1/&quot;&gt;Tutorial Part 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.jpalardy.com/posts/awk-tutorial-part-2/&quot;&gt;Tutorial Part 2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.jpalardy.com/posts/awk-tutorial-part-3/&quot;&gt;Tutorial Part 3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Why+would+you+want+to+do+this%3F&quot; name=&quot;Why+would+you+want+to+do+this%3F&quot;&gt;Why would you want to do this?&lt;/h2&gt;
&lt;p&gt;There are a number of reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;it&#039;s shorter to type&lt;/li&gt;
&lt;li&gt;it spawns one less process&lt;/li&gt;
&lt;li&gt;awk uses modern (read &amp;quot;Perl&amp;quot;) regular expressions, by default - like &lt;code&gt;grep -E&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;it&#039;s ready to &amp;quot;augment&amp;quot; with more awk&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;What+about+grep+-v%3F&quot; name=&quot;What+about+grep+-v%3F&quot;&gt;What about &lt;code&gt;grep -v&lt;/code&gt;?&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;grep -v&lt;/code&gt; can be done with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ [data is generated] | awk &#039;! /something/&#039;&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;Reference: &lt;a href=&quot;https://blog.jpalardy.com/posts/skip-grep-use-awk/&quot;&gt;jpalardy.com&lt;/a&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>Naming Schemes</title>
<link href="https://www.0ink.net/posts/2018/2018-07-04-Naming-schemes.html"></link>
<id>urn:uuid:2c03b2fa-79a4-31c8-bcc3-5d6b1ad0f37f</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This web site contains list of names of different topics.  This
can be used for naming schemes:
Naming Schemes
...]]></summary>
<content type="html">&lt;p&gt;This web site contains list of names of different topics.  This
can be used for naming schemes:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://namingschemes.com/Main_Page&quot;&gt;Naming Schemes&lt;/a&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>Build a VR app in 15 minutes</title>
<link href="https://www.0ink.net/posts/2018/2018-06-18-build-vr-app.html"></link>
<id>urn:uuid:c65f75dc-45d7-418e-9ae9-d633ee9679d3</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[In 15 minutes, you can develop a virtual reality application and run
it in a web browser, on a VR headset, or with Google Daydream.
The key is A-Frame, an open source toolkit built
by the Mozilla VR Team.
Test It
Open this link
...]]></summary>
<content type="html">&lt;p&gt;In 15 minutes, you can develop a virtual reality application and run
it in a web browser, on a VR headset, or with &lt;a href=&quot;https://vr.google.com/daydream/&quot;&gt;Google Daydream&lt;/a&gt;.
The key is &lt;a href=&quot;https://aframe.io/&quot;&gt;A-Frame&lt;/a&gt;, an open source toolkit built
by the &lt;a href=&quot;https://mozvr.com/&quot;&gt;Mozilla VR Team&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;Test+It&quot; name=&quot;Test+It&quot;&gt;Test It&lt;/h3&gt;
&lt;p&gt;Open &lt;a href=&quot;https://theta360developers.github.io/360gallery/&quot;&gt;this link&lt;/a&gt;
using Chrome or Firefox on your mobile phone.&lt;/p&gt;
&lt;p&gt;Put your phone into &lt;a href=&quot;https://vr.google.com/cardboard/&quot;&gt;Google Cardboard&lt;/a&gt;
and stare at a menu square to switch the 360-degree scene.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2018/vr-in-15min-1.png&quot; alt=&quot;vr-in-15mins-1&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;Fork+it&quot; name=&quot;Fork+it&quot;&gt;Fork it&lt;/h3&gt;
&lt;p&gt;Fork the &lt;a href=&quot;https://github.com/theta360developers/360gallery&quot;&gt;sample repository from GitHub&lt;/a&gt;.
Change directory into the repo.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2018/vr-in-15min-2.png&quot; alt=&quot;vr-in-15mins-2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If you have 360-degree images, you can drop them into the img/
sub-directory. If you don&#039;t have 360-degree images, you can get
started with the open source &lt;a href=&quot;http://hugin.sourceforge.net/&quot;&gt;Hugin&lt;/a&gt;
panorama photo stitcher. The boilerplate app includes &lt;a href=&quot;http://theta360.guide/community-document/community.html&quot;&gt;RICOH THETA media&lt;/a&gt;
I took at a meetup in San Francisco.&lt;/p&gt;
&lt;h3 id=&quot;Create+thumbnails&quot; name=&quot;Create+thumbnails&quot;&gt;Create thumbnails&lt;/h3&gt;
&lt;p&gt;The menus in the headset are standard images that are 240x240 pixels.
A-Frame handles the perspective shifts for you automatically.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2018/vr-in-15min-3.png&quot; alt=&quot;vr-in-15mins-3&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;Edit+code&quot; name=&quot;Edit+code&quot;&gt;Edit code&lt;/h3&gt;
&lt;p&gt;If you use the same image file names and overwrite 1.jpg in /img, you
do not need to edit the code at all. If you want to extend the program
or modify it with your own filenames, change the id and the src in
index.html to match your files.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;body&amp;gt;
  &amp;lt;a-scene&amp;gt;
    &amp;lt;a-assets&amp;gt;
      &amp;lt;img id=&quot;kieran&quot; src=&quot;img/1.jpg&quot;&amp;gt;
      &amp;lt;img id=&quot;kieran-thumb&quot; crossorigin=&quot;anonymous&quot; src=&quot;img/kieran-thumb.png&quot;&amp;gt;
      &amp;lt;img id=&quot;christian-thumb&quot; crossorigin=&quot;anonymous&quot; src=&quot;img/christian-thumb.png&quot;&amp;gt;
      &amp;lt;img id=&quot;eddie-thumb&quot; crossorigin=&quot;anonymous&quot; src=&quot;img/eddie-thumb.png&quot;&amp;gt;
      &amp;lt;audio id=&quot;click-sound&quot; crossorigin=&quot;anonymous&quot; src=&quot;https://cdn.aframe.io/360-image-gallery-boilerplate/audio/click.ogg&quot;&amp;gt;&amp;lt;/audio&amp;gt;
      &amp;lt;img id=&quot;christian&quot; crossorigin=&quot;anonymous&quot; src=&quot;img/2.jpg&quot;&amp;gt;
      &amp;lt;img id=&quot;eddie&quot; crossorigin=&quot;anonymous&quot; src=&quot;img/4.jpg&quot;&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Scroll down and edit the section for the menu links.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!-- 360-degree image. --&amp;gt;
&amp;lt;a-sky id=&quot;image-360&quot; radius=&quot;10&quot; src=&quot;#kieran&quot;&amp;gt;&amp;lt;/a-sky&amp;gt;

&amp;lt;!-- Image links. --&amp;gt;
&amp;lt;a-entity id=&quot;links&quot; layout=&quot;type: line; margin: 1.5&quot; position=&quot;0 -1 -4&quot;&amp;gt;
  &amp;lt;a-entity template=&quot;src: #link&quot; data-src=&quot;#christian&quot; data-thumb=&quot;#christian-thumb&quot;&amp;gt;&amp;lt;/a-entity&amp;gt;
  &amp;lt;a-entity template=&quot;src: #link&quot; data-src=&quot;#kieran&quot; data-thumb=&quot;#kieran-thumb&quot;&amp;gt;&amp;lt;/a-entity&amp;gt;
  &amp;lt;a-entity template=&quot;src: #link&quot; data-src=&quot;#eddie&quot; data-thumb=&quot;#eddie-thumb&quot;&amp;gt;&amp;lt;/a-entity&amp;gt;
&amp;lt;/a-entity&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Upload+to+GitHub+pages&quot; name=&quot;Upload+to+GitHub+pages&quot;&gt;Upload to GitHub pages&lt;/h3&gt;
&lt;p&gt;Add and commit your changes:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git add *
git commit -a -m ?changed images&#039;
git push&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Open your app on a mobile phone at &lt;code&gt;http://username.github.io/360gallery&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;Next+steps&quot; name=&quot;Next+steps&quot;&gt;Next steps&lt;/h3&gt;
&lt;p&gt;This is a brief taste of A-Frame to illustrate that WebVR is easy and
accessible to web developers. Go to &lt;a href=&quot;http://aframe.io&quot;&gt;aframe.io&lt;/a&gt; to see
more demos. Although the display of 360 images is not true VR, it is
easy, fun, and accessible today. Using 360 images is also a great way
to start to understand the basics of augmented reality.&lt;/p&gt;
&lt;p&gt;Take your own pictures with a standard camera and stitch them together
or buy or borrow a 360-degree camera. The camera I used supports
360-degree video files and live streaming.&lt;/p&gt;
&lt;h3 id=&quot;Troubleshooting&quot; name=&quot;Troubleshooting&quot;&gt;Troubleshooting&lt;/h3&gt;
&lt;p&gt;The application won&#039;t run from a local file that you open in your
browser. You must either run a local webserver like Apache2 or upload
it to an external site like GitHub Pages for testing.&lt;/p&gt;
&lt;p&gt;If you&#039;re using an Oculus Rift or HTC Vive, you may need to install
Firefox Nightly or experimental Chromium builds. See the current
status of your browser at &lt;a href=&quot;https://iswebvrready.org/&quot;&gt;Is WebVR Ready?&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;360-degree video works on desktop browsers. I&#039;ve experienced some
glitches on mobile devices. The technology is improving quickly.&lt;/p&gt;
&lt;p&gt;Source &lt;a href=&quot;https://opensource.com/life/16/11/build-virtual-reality-app&quot;&gt;OpenSource&lt;/a&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>Set your google account to automatically delete</title>
<link href="https://www.0ink.net/posts/2018/2018-06-17-google-auto-delete.html"></link>
<id>urn:uuid:235c8d5f-db64-2dd9-c456-f80810216f9f</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Want to share your family photos after your death, but take your
search history to the grave? All that and more is possible with
Google's Inactive Account Manager.
How You Can Control Your Information After Death
It's not nice to think about, but one day, you will die, along with
the keys to your online kingdom. And these days, those online accounts
...]]></summary>
<content type="html">&lt;p&gt;Want to share your family photos after your death, but take your
search history to the grave? All that and more is possible with
Google&#039;s Inactive Account Manager.&lt;/p&gt;
&lt;h3 id=&quot;How+You+Can+Control+Your+Information+After+Death&quot; name=&quot;How+You+Can+Control+Your+Information+After+Death&quot;&gt;How You Can Control Your Information After Death&lt;/h3&gt;
&lt;p&gt;It&#039;s not nice to think about, but one day, you will die, along with
the keys to your online kingdom. And these days, those online accounts
can hold a lot of stuff you may want to pass on.&lt;/p&gt;
&lt;p&gt;Your Google account has a feature tucked deep in the bowels of your
account settings called &amp;quot;Inactive Account Manager&amp;quot;. Although the
feature is several years old now, it&#039;s practically unknown among
Google users–in a casual survey of people outside our office who had
Google accounts, not one of them was aware of the feature.&lt;/p&gt;
&lt;p&gt;Inactive Account Manager is what fans of old spy movies and
psychological thrillers will immediately recognize as a &amp;quot;dead man&#039;s
switch&amp;quot;. Once activated if you do not interact with your Google
account in X amount of time, then Google&#039;s servers will automatically
either notify your trusted contacts and/or share specified data with
those select contacts. Or, at your instruction, it can wipe your account.&lt;/p&gt;
&lt;p&gt;In this way, you can ensure that things like family photos stored in
Google Photos are available to your family, that your spouse can will
have full access to your contacts to manage your business affairs, or
that anyone you wish to share your account with upon your demise or
incapacitation can access it legitimately and without resorting to
masquerading themselves as you.&lt;/p&gt;
&lt;h3 id=&quot;Setting+Up+the+Inactive+Account+Manager&quot; name=&quot;Setting+Up+the+Inactive+Account+Manager&quot;&gt;Setting Up the Inactive Account Manager&lt;/h3&gt;
&lt;p&gt;To set up Inactive Account Manager, make sure you&#039;re logged into your
Google account and visit this &lt;a href=&quot;https://www.google.com/settings/u/0/account/inactive&quot;&gt;page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can supply a contact phone number for the person (don&#039;t worry, they
won&#039;t get an immediate text indicating that you&#039;ve selected them, so
there won&#039;t be any awkward conversations about death triggered by
this process). Then you need to specify what Google data you want to
share with them. We&#039;d encourage you to apply this step selectively,
and not simply check &amp;quot;Select all&amp;quot;. Most of us would happily share our
Google Photo collections with our next of kin, after all, but would
prefer to take our search histories private.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2018/google-auto-delete-1.png&quot; alt=&quot;google-auto-delete-1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The final step is the weightiest one: selecting whether or not your
Google account will be wiped upon the completion of the timeout period.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2018/google-auto-delete-2.png&quot; alt=&quot;google-auto-delete-2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;There is no option to partially delete the data, so make this very
binary decision with care. You cannot, for example, wipe your search
history and email but leave your YouTube content and Blogger posts
intact for posterity. Once the countdown is complete, like a real
dead man&#039;s switch, the account data is gone forever.&lt;/p&gt;
&lt;p&gt;Source &lt;a href=&quot;https://www.howtogeek.com/273488/how-to-set-your-google-account-to-automatically-delete-or-share-upon-your-death/&quot;&gt;HowToGee&lt;/a&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>HTML Entities</title>
<link href="https://www.0ink.net/posts/2018/2018-06-16-html-entities.html"></link>
<id>urn:uuid:afd77851-3a37-7479-4740-d3779c83b92d</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[(remember the ampersand at the start and the semi-colon at the end of each &quot;tag&quot;)
| &Aacute; | &amp;Aacute; |
| &aacute; | &amp;aacute; |
| &Agrave; | &amp;Agrave; |
| &Acirc; | &amp;Acirc; |
| &agrave; | &amp;agrave; |
...]]></summary>
<content type="html">&lt;p&gt;(remember the ampersand at the start and the semi-colon at the end of each &amp;quot;tag&amp;quot;)&lt;/p&gt;
&lt;p&gt;| &amp;Aacute; | &amp;amp;Aacute; |
| &amp;aacute; | &amp;amp;aacute; |
| &amp;Agrave; | &amp;amp;Agrave; |
| &amp;Acirc; | &amp;amp;Acirc; |
| &amp;agrave; | &amp;amp;agrave; |
| &amp;Acirc; | &amp;amp;Acirc; |
| &amp;acirc; | &amp;amp;acirc; |
| &amp;Auml; | &amp;amp;Auml; |
| &amp;auml; | &amp;amp;auml; |
| &amp;Atilde; | &amp;amp;Atilde; |
| &amp;atilde; | &amp;amp;atilde; |
| &amp;Aring; | &amp;amp;Aring; |
| &amp;aring; | &amp;amp;aring; |
| &amp;AElig; | &amp;amp;AElig; |
| &amp;aelig; | &amp;amp;aelig; |
| &amp;Ccedil; | &amp;amp;Ccedil; |
| &amp;ccedil; | &amp;amp;ccedil; |
| &amp;ETH; | &amp;amp;ETH; |
| &amp;eth; | &amp;amp;eth; |
| &amp;Eacute; | &amp;amp;Eacute; |
| &amp;eacute; | &amp;amp;eacute; |
| &amp;Egrave; | &amp;amp;Egrave; |
| &amp;egrave; | &amp;amp;egrave; |
| &amp;Ecirc; | &amp;amp;Ecirc; |
| &amp;ecirc; | &amp;amp;ecirc; |
| &amp;Euml; | &amp;amp;Euml; |
| &amp;euml; | &amp;amp;euml; |
| &amp;Iacute; | &amp;amp;Iacute; |
| &amp;iacute; | &amp;amp;iacute; |
| &amp;Igrave; | &amp;amp;Igrave; |
| &amp;igrave; | &amp;amp;igrave; |
| &amp;Icirc; | &amp;amp;Icirc; |
| &amp;Iuml; | &amp;amp;Iuml; |
| &amp;iuml; | &amp;amp;iuml; |
| &amp;ntilde; | &amp;amp;ntilde; |
| &amp;Oacute; | &amp;amp;Oacute; |
| &amp;oacute; | &amp;amp;oacute; |
| &amp;Ograve; | &amp;amp;Ograve; |
| &amp;ograve; | &amp;amp;ograve; |
| &amp;Ocirc; | &amp;amp;Ocirc; |
| &amp;ocirc; | &amp;amp;ocirc; |
| &amp;Ouml; | &amp;amp;Ouml; |
| &amp;ouml; | &amp;amp;ouml; |
| &amp;Otilde; | &amp;amp;Otilde; |
| &amp;otilde; | &amp;amp;otilde; |
| &amp;Oslash; | &amp;amp;Oslash; |
| &amp;oslash; | &amp;amp;oslash; |
| &amp;szlig; | &amp;amp;szlig; |
| &amp;THORN; | &amp;amp;THORN; |
| &amp;thorn; | &amp;amp;thorn; |
| &amp;Uacute; | &amp;amp;Uacute; |
| &amp;uacute; | &amp;amp;uacute; |
| &amp;Ugrave; | &amp;amp;Ugrave; |
| &amp;ugrave; | &amp;amp;ugrave; |
| &amp;Ucirc; | &amp;amp;Ucirc; |
| &amp;ucirc; | &amp;amp;ucirc; |
| &amp;Uuml; | &amp;amp;Uuml; |
| &amp;uuml; | &amp;amp;uuml; |
| &amp;Yacute; | &amp;amp;Yacute; |
| &amp;yacute; | &amp;amp;yacute; |
| &amp;yuml; | &amp;amp;yuml; |
| &amp;copy; | &amp;amp;copy; |
| &amp;reg; | &amp;amp;reg; |
| &amp;trade; | &amp;amp;trade; |
| &amp;amp; | &amp;amp;amp; |
| &amp;lt; | &amp;amp;lt; |
| &amp;gt; | &amp;amp;gt; |
| &amp;euro; | &amp;amp;euro; |
| &amp;cent; | &amp;amp;cent; |
| &amp;pound; | &amp;amp;pound; |
| &amp;quot; | &amp;amp;quot; |
| &amp;lsquo; | &amp;amp;lsquo; |
| &amp;rsquo; | &amp;amp;rsquo; |
| &amp;ldquo; | &amp;amp;ldquo; |
| &amp;rdquo; | &amp;amp;rdquo; |
| &amp;laquo; | &amp;amp;laquo; |
| &amp;raquo; | &amp;amp;raquo; |
| &amp;mdash; | &amp;amp;mdash; |
| &amp;ndash; | &amp;amp;ndash; |
| &amp;deg; | &amp;amp;deg; |
| &amp;plusmn; | &amp;amp;plusmn; |
| &amp;frac14; | &amp;amp;frac14; |
| &amp;frac12; | &amp;amp;frac12; |
| &amp;frac34; | &amp;amp;frac34; |
| &amp;times; | &amp;amp;times; |
| &amp;divide; | &amp;amp;divide; |
| &amp;alpha; | &amp;amp;alpha; |
| &amp;beta; | &amp;amp;beta; |
| &amp;infin; | &amp;amp;infin; |
| &amp;nbsp; | &amp;amp;nbsp; |&lt;/p&gt;
&lt;p&gt;Reference:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.starr.net/is/type/htmlcodes.html&quot;&gt;International Accent Marks and Diacriticals&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.techdictionary.com/ascii.html&quot;&gt;ASCII Hex&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>3 Open Source Password Managers</title>
<link href="https://www.0ink.net/posts/2018/2018-06-15-password-managers.html"></link>
<id>urn:uuid:aa388af9-33b3-145d-9d58-5fc3566719d2</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Keep your data and accounts safe by using a secure open source
password manager to store unique, complex passwords.
Maintaining complex, unique passwords for each site and service you
use is among the most common pieces of advice that security
professionals provide to the public every year.
Yet no matter how many times it is said, it seems like a week doesn't
...]]></summary>
<content type="html">&lt;p&gt;Keep your data and accounts safe by using a secure open source
password manager to store unique, complex passwords.&lt;/p&gt;
&lt;p&gt;Maintaining complex, unique passwords for each site and service you
use is among the most common pieces of advice that security
professionals provide to the public every year.&lt;/p&gt;
&lt;p&gt;Yet no matter how many times it is said, it seems like a week doesn&#039;t
go by where a high-profile hacking story hits the news, revealing that
users of the service in question more often than not had such secure
passwords as &amp;quot;12345&amp;quot; or &amp;quot;password&amp;quot; as the only wall of protection on
their account.&lt;/p&gt;
&lt;p&gt;Or perhaps a user offers up just enough variation on the classic
password selection to get past the minimal rules of the service.
Unfortunately, &amp;quot;Pa$$w0rd!&amp;quot; isn&#039;t secure in any meaningful way, either.
At this point, almost every variation of words and phrases strung
together with a few numbers or substitutions is simply too easy for a
password cracking tool to make its way through, and the shorter the
password, the easier.&lt;/p&gt;
&lt;p&gt;The best passwords are long, random or pseudo-random combinations of
every possible character allowed, with a different password for each
unique use. But how could a normal person remember the hundreds or
even thousands of individual passwords associated with each account
they&#039;ve ever created? The short answer is: they can&#039;t. And don&#039;t even
think about writing a password down in plain text, whether in the
physical world or the digital.&lt;/p&gt;
&lt;p&gt;Perhaps the easiest way to keep track of these complex, unique
passwords is with a password manager, which provides easy access to
strong encryption. While proprietary commercial solutions like LastPass
are popular, there are several open source solutions as well. And with
passwords, being able to audit the source code of your password manager
is especially important, as it helps ensure that your passwords are
encrypted properly and are not vulnerable to backdoors.&lt;/p&gt;
&lt;p&gt;So without further ado, here are a few open source password managers
we hope you will consider.&lt;/p&gt;
&lt;h3 id=&quot;KeePass&quot; name=&quot;KeePass&quot;&gt;KeePass&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;http://keepass.info/&quot;&gt;KeePass&lt;/a&gt; is a GPLv2-licensed password manager,
primarily designed for Windows but also running elsewhere. KeePass
offers multiple strong encryption options, easy exports, multiple
user keys, advanced searching features, and more. Designed for desktop
use, there are plugins that allow direct use from your web browser,
and it can run from a USB stick if you&#039;d prefer to physically carry
your passwords from machine to machine.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.keepassx.org/&quot;&gt;KeePassX&lt;/a&gt;, which started as a Linux port
of KeePass, is another project you may consider. KeyPassX is compatible
with KeePass 2 password files, and has also been ported to run on
different operating systems.&lt;/p&gt;
&lt;h3 id=&quot;Padlock&quot; name=&quot;Padlock&quot;&gt;Padlock&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://padlock.io/&quot;&gt;Padlock&lt;/a&gt; is a very new entrant into the world of
open source password managers. Currently available for Windows, Mac,
iOS, and Android, with a Linux client in the works, Padlock is
designed as a &amp;quot;minimalist&amp;quot; password manager. Its
&lt;a href=&quot;https://github.com/MaKleSoft/padlock&quot;&gt;source&lt;/a&gt; is available on GitHub.
The project is also developing a
&lt;a href=&quot;https://github.com/maklesoft/padlock-cloud&quot;&gt;cloud backend&lt;/a&gt;, also open
source, which will be a welcomed addition to anyone tired of managing
password files or setting up syncing across multiple computers.&lt;/p&gt;
&lt;h3 id=&quot;Passbolt&quot; name=&quot;Passbolt&quot;&gt;Passbolt&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://padlock.io/&quot;&gt;Passbolt&lt;/a&gt; is another relatively new option, with
plugins available for Firefox and Chrome and mobile and command-line
options on the way. Based on OpenPGP, you can check out its online
&lt;a href=&quot;https://demo.passbolt.com/auth/login&quot;&gt;demo&lt;/a&gt; which shows off some of
the features (you&#039;ll need to install the plugin for your browser, though).
You can check out the source code on &lt;a href=&quot;https://github.com/passbolt&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Using a password manager that you trust alongside complex passwords is
not a substitute for taking other security precautions, nor is it
foolproof. But for many users, it can be an important part of keeping
your digital life secured. These definitely aren&#039;t the only options
out there. There are some older options, like &lt;a href=&quot;https://clipperz.is/&quot;&gt;Clipperz&lt;/a&gt;
and &lt;a href=&quot;https://pwsafe.org/&quot;&gt;Password Safe&lt;/a&gt;, and web-based tools like
&lt;a href=&quot;https://github.com/tildaslash/RatticWeb&quot;&gt;RatticDB&lt;/a&gt; that I would be
interested to try out.&lt;/p&gt;
&lt;p&gt;Source &lt;a href=&quot;https://opensource.com/article/16/12/password-managers&quot;&gt;opensource.com&lt;/a&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>How to encrypt linux partitions with LUKS</title>
<link href="https://www.0ink.net/posts/2018/2018-06-14-linux-disk-encryption.html"></link>
<id>urn:uuid:d6550883-f9c9-5b23-c242-004da20e2ca9</id>
<updated>2023-09-29T00:00:00+02:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[There are plenty of reasons why people would need to encrypt a
partition. Whether they're rooted it in privacy, security, or
confidentiality, setting up a basic encrypted partition on a Linux
system is fairly easy. This is especially true when using LUKS, since
its functionality is built directly into the kernel.
Installing Cryptsetup
...]]></summary>
<content type="html">&lt;p&gt;There are plenty of reasons why people would need to encrypt a
partition. Whether they&#039;re rooted it in privacy, security, or
confidentiality, setting up a basic encrypted partition on a Linux
system is fairly easy. This is especially true when using LUKS, since
its functionality is built directly into the kernel.&lt;/p&gt;
&lt;h2 id=&quot;Installing+Cryptsetup&quot; name=&quot;Installing+Cryptsetup&quot;&gt;Installing Cryptsetup&lt;/h2&gt;
&lt;h3 id=&quot;Debian%2FUbuntu&quot; name=&quot;Debian%2FUbuntu&quot;&gt;Debian/Ubuntu&lt;/h3&gt;
&lt;p&gt;On both Debian and Ubuntu, the &lt;code&gt;cryptsetup&lt;/code&gt; utility is easily
available in the repositories. The same should be true for Mint or
any of their other derivatives.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo apt-get install cryptsetup&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;CentOS%2FFedora&quot; name=&quot;CentOS%2FFedora&quot;&gt;CentOS/Fedora&lt;/h3&gt;
&lt;p&gt;Again, the required tools are easily available in both CentOS and
Fedora. These distributions break them down into multiple packages,
but they can still be easily installed using &lt;code&gt;yum&lt;/code&gt; and &lt;code&gt;dnf&lt;/code&gt;
respectively.&lt;/p&gt;
&lt;h4 id=&quot;CentOS&quot; name=&quot;CentOS&quot;&gt;CentOS&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;# yum install crypto-utils cryptsetup-luks cryptsetup-luks-devel cryptsetup-luks-libs&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;Fedora&quot; name=&quot;Fedora&quot;&gt;Fedora&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;# dnf install crypto-utils cryptsetup cryptsetup-luks&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;OpenSUSE&quot; name=&quot;OpenSUSE&quot;&gt;OpenSUSE&lt;/h3&gt;
&lt;p&gt;OpenSUSE is more like the Debian based distributions, including
everything that you need with &lt;code&gt;cryptsetup&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# zypper in cryptsetup&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Arch+Linux&quot; name=&quot;Arch+Linux&quot;&gt;Arch Linux&lt;/h3&gt;
&lt;p&gt;Arch stays true to its &amp;quot;keep it simple&amp;quot; philosophy here as well.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# pacman -S cryptsetup&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Gentoo&quot; name=&quot;Gentoo&quot;&gt;Gentoo&lt;/h3&gt;
&lt;p&gt;The main concern that Gentoo users should have when installing the
tools necessary for using LUKS is whether or not their kernel has
support. This guide is not going to cover that part, but just be
aware that kernel support is a factor. If your kernel does support
LUKS, you can just emerge the package.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# emerge --ask cryptsetup&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Setting+Up+The+Partition&quot; name=&quot;Setting+Up+The+Partition&quot;&gt;Setting Up The Partition&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;WARNING:&lt;/em&gt; &lt;strong&gt;The following will erase all data on the partition being
used and will make it unrecoverable. Proceed with caution.&lt;/strong&gt;
From here on, none of this is distribution specific. It will all work
well with any distribution.The defaults provided are actually quite
good, but they can easily be customized. If you really aren&#039;t
comfortable playing with them, don&#039;t worry. If you do know what you
want to do, feel free.&lt;/p&gt;
&lt;p&gt;The basic options are as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;--cypher:  This determines the cryptographic cypher used on the
partition.  The default option is aes-xts-plain64&lt;/li&gt;
&lt;li&gt;--key-size: The length of the key used.  The default is 256&lt;/li&gt;
&lt;li&gt;--hash: Chooses the hash algorithm used to derive the key.  The
default is sha256.&lt;/li&gt;
&lt;li&gt;--time: The time used for passphrase processing.  The default is
2000 milliseconds.&lt;/li&gt;
&lt;li&gt;--use-random/--use-urandom: Determines the random number generator
used.  The default is --use-random.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, a basic command with no options would look like the line below.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# cryptsetup luksFormat /dev/sdb1&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Obviously, you&#039;d want to use the path to whichever partition that
you&#039;re encrypting. If you do want to use options, it would look like
the following.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# cryptsetup -c aes-xts-plain64 --key-size 512 --hash sha512 --time 5000 --use-urandom /dev/sdb1&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Cryptsetup&lt;/code&gt; will ask for a passphrase. Choose one that is both
secure and memorable. If you forget it, your data &lt;em&gt;will be lost.&lt;/em&gt;
That will probably take a few seconds to complete, but when it&#039;s
done, it will have successfully converted your partition into an
encrypted LUKS volume.&lt;/p&gt;
&lt;p&gt;Next, you have to open the volume onto the device mapper. This is the
stage at which you will be prompted for your passphrase. You can
choose the name that you want your partition mapped under. It doesn&#039;t
really matter what it is, so just pick something that will be easy to
remember and use.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# cryptsetup open /dev/sdb1 encrypted&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once the drive is mapped, you&#039;ll have to choose a filesystem type for
you partition. Creating that filesystem is the same as it would be on
a regular partition.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# mkfs.ext4 /dev/mapper/encrypted&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The one difference between creating the filesystem on a regular
partition and an encrypted one is that you will use the path to the
mapped name instead of the actual partition location. Wait for the
filesystem to be created. Then, the drive will be ready for use.&lt;/p&gt;
&lt;h2 id=&quot;Mounting+and+Unmounting&quot; name=&quot;Mounting+and+Unmounting&quot;&gt;Mounting and Unmounting&lt;/h2&gt;
&lt;p&gt;Manually mounting and unmounting encrypted partitions is almost the
same as doing so with normal partitions. There is one more step in
each direction, though. First, to manually mount an encrypted
partition, run the command below.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# cryptsetup --type luks open /dev/sdb1 encrypted
# mount -t ext4 /dev/mapper/encrypted /place/to/mount&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unmounting the partition is the same as a normal one, but you have to
close the mapped device too.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# umount /place/to/mount
# cryptsetup close encrypted&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Closing&quot; name=&quot;Closing&quot;&gt;Closing&lt;/h2&gt;
&lt;p&gt;There&#039;s plenty more, but when talking about security and encryption,
things run rather deep. This guide provides the basis for encrypting
and using encrypted partitions, which is an important first step that
shouldn&#039;t be discounted. There will definitely be more coming in this
area, so be sure to check back, if you&#039;re interested in going a bit
deeper.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Source &lt;a href=&quot;https://linuxconfig.org/basic-guide-to-encrypting-linux-partitions-with-luks&quot;&gt;linuxconfig&lt;/a&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>Open Source Alternatives to Visio</title>
<link href="https://www.0ink.net/posts/2018/2018-06-11-visio-alternatives.html"></link>
<id>urn:uuid:1677a68e-2ad9-49de-2f37-0fd302a42399</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Need to create diagrams, flowcharts, circuits, or other kinds of
entity-relationship models? Microsoft Visio is without a doubt the
best software for that, but that doesn't mean it's the best choice
for you.
Visio may be the industry standard in the corporate world, but it
comes with a huge drawback: it's expensive ($299 for the standard
...]]></summary>
<content type="html">&lt;p&gt;Need to create diagrams, flowcharts, circuits, or other kinds of
entity-relationship models? Microsoft Visio is without a doubt the
best software for that, but that doesn&#039;t mean it&#039;s the best choice
&lt;em&gt;for you.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Visio may be the industry standard in the corporate world, but it
comes with a huge drawback: it&#039;s expensive ($299 for the standard
version as of this writing). Can&#039;t afford that? Then you &#039;ll be happy
to know that several open source alternatives exist for the low, low
price of FREE.&lt;/p&gt;
&lt;p&gt;We&#039;re going to highlight the two best ones here, but if you don&#039;t like
them for whatever reason, you can scroll down to the bottom of the
article for even more options to explore.&lt;/p&gt;
&lt;h3 id=&quot;Diagram+Creation+with+Dia&quot; name=&quot;Diagram+Creation+with+Dia&quot;&gt;Diagram Creation with Dia&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;http://dia-installer.de/&quot;&gt;Dia&lt;/a&gt; has been the go-to Visio alternative
for many years. What I like
most about it is the first impression that you get when it launches:
clean, simple, with an interface that&#039;s familiar and easy to navigate.
Quite reminiscent of Visio, in fact:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2018/dia-1.png&quot; alt=&quot;dia-1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You&#039;ll be able to create your first diagram in mere minutes.
Drag-and-drop a few symbols onto the canvas, then connect them using
the various types available in the toolbox: lines, zigzags, arcs,
circles, curves, etc.&lt;/p&gt;
&lt;p&gt;Dia also supports layers, making it a lot easier to manage complex
charts, and moving elements between layers is as simple as hitting a
hotkey.&lt;/p&gt;
&lt;p&gt;Snap to grid, easy resizing, text labels, image insertions -- Dia has
it all. Anything you can do in Visio can be done in Dia as well. The
only real downside is that Dia can&#039;t open Visio VSD files, but it can
handle most other diagramming formats like XML, EPS, and SVG.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Download -- &lt;a href=&quot;http://dia-installer.de/&quot;&gt;Dia&lt;/a&gt;&lt;/em&gt; (Free)&lt;/p&gt;
&lt;h3 id=&quot;Diagram+Creation+with+LibreOffice+Draw&quot; name=&quot;Diagram+Creation+with+LibreOffice+Draw&quot;&gt;Diagram Creation with LibreOffice Draw&lt;/h3&gt;
&lt;p&gt;Have you heard of &lt;a href=&quot;http://www.libreoffice.org/&quot;&gt;LibreOffice&lt;/a&gt;? As far
as open source competitors to Microsoft Office go, you won&#039;t find a
more solid and robust alternative.&lt;/p&gt;
&lt;p&gt;LibreOffice is far from perfect, but it&#039;s a respectable option for
fans of open source software. The app that should interest you is
&lt;em&gt;LibreOffice Draw&lt;/em&gt;, the Visio counterpart in this office suite.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2018/draw-1.png&quot; alt=&quot;draw-1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;LibreOffice Draw supplies two things for you: shapes and lines. You
use the shapes to represent diagram entities, and you use the lines to
connect them according to the entity relationships. It&#039;s perfect for
creating flowcharts, but you can do more with it if you want (like
desktop publishing or PDF editing).&lt;/p&gt;
&lt;p&gt;First you have to open the Drawing toolbar, which you can do through
&lt;em&gt;View &amp;gt; Toolbars &amp;gt; Drawing&lt;/em&gt;. Grid snapping is on by default, but you&#039;ll
want to change the snapping sensitivity by going to
&lt;em&gt;Tools &amp;gt; Options&lt;/em&gt;, navigate to &lt;em&gt;LibreOffice Draw &amp;gt; Grid&lt;/em&gt;, change the
values under &lt;em&gt;Resolution&lt;/em&gt; to your intended grid size, and change the
values under &lt;em&gt;Subdivision&lt;/em&gt; to &lt;em&gt;1&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;LibreOffice is surprisingly easy to use once it&#039;s set up properly. You
can draw shapes, connectors, lines, curves, symbols, arrows, thought
bubbles, and even 3D objects. If you&#039;re already using LibreOffice as
your main office suite, forget Dia and learn to use Draw instead. The
learning curve isn&#039;t much worse at all, and you can use it for more
than just diagrams.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Download -- &lt;a href=&quot;http://www.libreoffice.org/download/libreoffice-fresh/&quot;&gt;LibreOffice&lt;/a&gt;&lt;/em&gt; (Free)&lt;/p&gt;
&lt;h3 id=&quot;Other+Alternatives+to+Visio&quot; name=&quot;Other+Alternatives+to+Visio&quot;&gt;Other Alternatives to Visio&lt;/h3&gt;
&lt;p&gt;Dia and Draw may be the best available right now, but a quick web
search will turn up plenty of competitors that are just as good in
many ways. Keep in mind that these are NOT open source unless
specifically noted in the description.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.yworks.com/products/yed&quot;&gt;yEd Graph Editor&lt;/a&gt; - Very similar
to Dia, except much more powerful and proportionally harder to use.
It has an automatic layout feature that can instantly rearrange a
diagram to be clutter-free and more readable, which is fantastic for
big and complex flowcharts.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.lucidchart.com/&quot;&gt;LucidChart&lt;/a&gt; - A very solid alternative
to Visio in a lot of ways. It&#039;s web-based, so you can access it from
anywhere, and packed full of features that make diagramming easy.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.draw.io/&quot;&gt;draw.io&lt;/a&gt; - A no-login-required web-based
diagramming tool that may not be the slickest in appearance, but
can certainly get the job done. Diagrams can be saved to Dropbox,
Google Drive, OneDrive, or locally. The interface is clean, the
results are acceptable, and it&#039;s open source.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Reference: &lt;a href=&quot;http://www.makeuseof.com/tag/a-free-open-source-alternative-to-microsoft-visio/&quot;&gt;makeuseof&lt;/a&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>10 tips for making documentation crystal clear</title>
<link href="https://www.0ink.net/posts/2018/2018-06-10-documentation-tips.html"></link>
<id>urn:uuid:6bd7769e-2f0f-a99e-4874-96b5031e910b</id>
<updated>2024-10-22T00:00:00+02:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[So you've some written excellent documentation. Now what? Now it's
time to go back and edit it. When you first sit down to write your
documentation, you want to focus on what you're trying to say instead
of how you're saying it, but once that first draft is done it's time
to go back and polish it up a little.
One of my favorite ways to edit is to read what I've written aloud.
...]]></summary>
<content type="html">&lt;p&gt;So you&#039;ve some written excellent documentation. Now what? Now it&#039;s
time to go back and edit it. When you first sit down to write your
documentation, you want to focus on what you&#039;re trying to say instead
of how you&#039;re saying it, but once that first draft is done it&#039;s time
to go back and polish it up a little.&lt;/p&gt;
&lt;p&gt;One of my favorite ways to edit is to read what I&#039;ve written aloud.
That&#039;s the best way to catch awkward phrasing or sentence structure
that might not stand out when you&#039;re reading it to yourself. If it
sounds good when you read it aloud, it probably is. If your
documentation happens to include instructions, you can watch someone
try to follow them. This provides good feedback on what steps are
missing or unclear, particularly if the person is unfamiliar with the
subject.&lt;/p&gt;
&lt;h3 id=&quot;Active+vs.+passive+voice&quot; name=&quot;Active+vs.+passive+voice&quot;&gt;Active vs. passive voice&lt;/h3&gt;
&lt;p&gt;You should prefer the active voice in most cases. It&#039;s okay to be
direct. How do you check for passive voice? Insert the words
&amp;quot;by zombies&amp;quot;. For example, it is much clearer to say
&amp;quot;If you click &#039;yes&#039;, you will delete your data&amp;quot; instead of &amp;quot;If you
click &#039;yes&#039;, data will be deleted.&amp;quot; Apply the zombie test to these two
examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;quot;If you click &#039;yes&#039;, you will delete your data by zombies.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;If you click &#039;yes&#039;, data will be deleted by zombies.&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the first example there is no doubt that you are the actor. The
second example shows that there is room for misinterpretation. Make it
very clear what actors perform actions.&lt;/p&gt;
&lt;h3 id=&quot;Eliminate+jargon&quot; name=&quot;Eliminate+jargon&quot;&gt;Eliminate jargon&lt;/h3&gt;
&lt;p&gt;Some jargon terms are unavoidable, but for the sake of clarity you
should avoid them as much as possible. Linking to the definition of a
term the first time you use it is acceptable, and you should also
write your own brief definition. You don&#039;t want to rely on the
availability of external sites, or make your readers jump through too
many hoops to understand your documentation.&lt;/p&gt;
&lt;h3 id=&quot;Check+for+common+mistakes&quot; name=&quot;Check+for+common+mistakes&quot;&gt;Check for common mistakes&lt;/h3&gt;
&lt;p&gt;Question everything you think you know, and take advantage of having
the world at your fingertips and look up everything. For example,
&amp;quot;e.g.&amp;quot; means &amp;quot;for example&amp;quot; and &amp;quot;i.e.&amp;quot; means &amp;quot;in other words&amp;quot;.
&amp;quot;Effect&amp;quot; is a noun (except when it isn&#039;t, as the
&lt;a href=&quot;https://xkcd.com/326/&quot;&gt;comic xkcd demonstrates&lt;/a&gt;), and &amp;quot;affect&amp;quot; is a
verb. Use &amp;quot;which&amp;quot; when a clause can be removed from the sentence
without a change in meaning, and &amp;quot;that&amp;quot; when it cannot.&lt;/p&gt;
&lt;h3 id=&quot;Remove+dangling+modifiers&quot; name=&quot;Remove+dangling+modifiers&quot;&gt;Remove dangling modifiers&lt;/h3&gt;
&lt;p&gt;You have created a dangling modifier when it is unclear which object
is being modified by a word or phrase. A classic example is &amp;quot;Hungry,
the leftover food was devoured&amp;quot;. Is Hungry the name of the food? Add
a comma after &amp;quot;food&amp;quot; if that is your meaning. If not, rewrite it to
make your meaning unambiguous: &amp;quot;Your author was hungry and devoured
the leftover food.&amp;quot; Organize your sentences carefully; don&#039;t force your
readers to guess your meaning.&lt;/p&gt;
&lt;h3 id=&quot;Check+your+style+guide&quot; name=&quot;Check+your+style+guide&quot;&gt;Check your style guide&lt;/h3&gt;
&lt;p&gt;If your project or company has a style guide for documentation, check
that what you&#039;ve written conforms to it. One common mistake is the
inappropriate abbreviation of company and project names.&lt;/p&gt;
&lt;h3 id=&quot;Avoid+unclear+words&quot; name=&quot;Avoid+unclear+words&quot;&gt;Avoid unclear words&lt;/h3&gt;
&lt;p&gt;Do you know how many times I deleted words like &amp;quot;often&amp;quot; and &amp;quot;some&amp;quot;
while writing this article? I don&#039;t know exactly how many, but I know
it&#039;s a non-zero number. Use words with specific meanings. This is
particularly important when you&#039;re trying to convince your reader that
what you&#039;re telling them is important. If I say &amp;quot;Following these tips
will make your writing better&amp;quot;, that&#039;s less convincing than &amp;quot;Following
these tips will increase financial contributions to your project by
45%.&amp;quot; If you catch yourself using vague words, ask yourself if you
really understand your topic, or if perhaps you&#039;re trying to hide
something.&lt;/p&gt;
&lt;h3 id=&quot;Check+the+word+order&quot; name=&quot;Check+the+word+order&quot;&gt;Check the word order&lt;/h3&gt;
&lt;p&gt;The English language does not have a formally-defined ordering
structure for modifying nouns, but there is an informal structure
which is described in this &lt;a href=&quot;https://twitter.com/MattAndersonNYT/status/772002757222002688&quot;&gt;Tweet by Matthew Anderson&lt;/a&gt;:
Opinion-size-age-shape-color-origin-material-purpose Noun. It&#039;s
something native English speakers know, but don&#039;t know we know. Peter
Sokolowski replied with advice to &lt;a href=&quot;https://twitter.com/PeterSokolowski/status/773186018317131776&quot;&gt;Put the &amp;quot;nounier&amp;quot; words closer to the noun&lt;/a&gt;.
If that doesn&#039;t help you read the discussion to his reply, which has
numerous examples and explanations.&lt;/p&gt;
&lt;h3 id=&quot;Remove+words+like+%26quot%3Bjust%26quot%3B+and+%26quot%3Bsimply%26quot%3B&quot; name=&quot;Remove+words+like+%26quot%3Bjust%26quot%3B+and+%26quot%3Bsimply%26quot%3B&quot;&gt;Remove words like &amp;quot;just&amp;quot; and &amp;quot;simply&amp;quot;&lt;/h3&gt;
&lt;p&gt;Technology is not as simple as we like to pretend it is. If you tell
your readers that something is simple and then they can&#039;t do it, what
are they to think of themselves? Unless you&#039;re writing an infomercial
for the latest must-have kitchen gadget, leave out those words.&lt;/p&gt;
&lt;h3 id=&quot;Check+your+pronouns&quot; name=&quot;Check+your+pronouns&quot;&gt;Check your pronouns&lt;/h3&gt;
&lt;p&gt;When you say &amp;quot;we&amp;quot;, who do you really mean? I have seen documentation
written in what I call &amp;quot;cooking show style&amp;quot;, where &amp;quot;Next we click the
whatchamadoozit to fribble the wozulator.&amp;quot; When you&#039;re writing in a
support context it is especially important to be clear who does what.
If you tell someone &amp;quot;We can change that setting&amp;quot;, they expect that
you will do it for them, and not that they can do it with your
guidance. As a general rule, I avoid using first person (I/we) except
when I am talking about myself as the author or the organization I am
representing. When in doubt, refer to yourself in the third person
(e.g. &amp;quot;The author suggests you refer to yourself in the third person&amp;quot;).
It might sound overly formal, but it is clear.&lt;/p&gt;
&lt;h3 id=&quot;Remove+split+infinitives&quot; name=&quot;Remove+split+infinitives&quot;&gt;Remove split infinitives&lt;/h3&gt;
&lt;p&gt;Don&#039;t put words in between &amp;quot;to&amp;quot; and a verb. Your documentation is on
a mission to go boldly where no docs have done before. (Starship
captains are excused from this rule).&lt;/p&gt;
&lt;p&gt;Reference: &lt;a href=&quot;https://opensource.com/life/16/11/tips-for-clear-documentation&quot;&gt;opensource life&lt;/a&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>Project Requirements</title>
<link href="https://www.0ink.net/posts/2018/2018-06-07-project-requirements.html"></link>
<id>urn:uuid:02ead301-0527-c89d-c77b-213dd279175d</id>
<updated>2022-11-02T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This is sometimes so true!

...]]></summary>
<content type="html">&lt;p&gt;This is sometimes so true!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2018/requirements_accavdar.jpg&quot; alt=&quot;Project Requirements Image&quot; /&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>Ascii Art Tools</title>
<link href="https://www.0ink.net/posts/2018/2018-06-07-ascii-art.html"></link>
<id>urn:uuid:af44fe69-f151-bdf0-63c6-0fbfd814661c</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Here are some resources dealling with ASCII art...

AsciiToSVG - PHP code
to convert ascii art into SVG.
AsciiFlow - Web App implement an ascii art
editor.
...]]></summary>
<content type="html">&lt;p&gt;Here are some resources dealling with ASCII art...&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/dhobsd/asciitosvg&quot;&gt;AsciiToSVG&lt;/a&gt; - PHP code
to convert ascii art into SVG.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://asciiflow.com/&quot;&gt;AsciiFlow&lt;/a&gt; - Web App implement an ascii art
editor.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://search.cpan.org/dist/App-Asciio/lib/App/Asciio.pm&quot;&gt;Asciio&lt;/a&gt;
A perl application allows you to draw ASCII diagrams in a modern
(but simple) graphical interface.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://ditaa.sourceforge.net/&quot;&gt;ditaa&lt;/a&gt; - Java based ascii art to PNG
converter.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/christiangoltz/shaape&quot;&gt;shaape&lt;/a&gt; - Shaape is an
ascii art to image converter designed to be used with asciidoc.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://blockdiag.com/en/index.html&quot;&gt;blockdiag&lt;/a&gt; - blockdiag and its
family generate diagram images from simple text files. (Python)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A few more added ones:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/blampe/goat&quot;&gt;GoAT&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/yuzutech/kroki&quot;&gt;kroki&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mermaid-js.github.io/mermaid/#/&quot;&gt;mermaid-js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/aafigure/aafigure&quot;&gt;aafigure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/ivanceras/svgbob&quot;&gt;svgbob&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>6 Cloud-Based Tools To Help You Build A Web App With Ease</title>
<link href="https://www.0ink.net/posts/2018/2018-06-06-web-app-build-tools.html"></link>
<id>urn:uuid:f78895c6-df12-5688-010f-88c02204915b</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[In just a relatively short amount of time, building mobile apps has
transformed from a process that included lots of knowledge in
developing into something that almost anyone can do. Cloud-based tools
are quickly becoming the norm for app developers, and these are some
of the highest recommended tools, each one being ideal for certain
developers.
...]]></summary>
<content type="html">&lt;p&gt;In just a relatively short amount of time, building mobile apps has
transformed from a process that included lots of knowledge in
developing into something that almost anyone can do. Cloud-based tools
are quickly becoming the norm for app developers, and these are some
of the highest recommended tools, each one being ideal for certain
developers.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;http://diy.como.com/features/&quot;&gt;Conduit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://softarex.com/&quot;&gt;Softarex&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.appery.io/&quot;&gt;Appery.io&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://codiqa.com/&quot;&gt;Codiqa&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.knackhq.com/&quot;&gt;Knack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.kinvey.com/&quot;&gt;Kinvey&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Source: &lt;a href=&quot;http://www.lifehack.org/468599/6-cloud-based-tools-to-help-you-build-a-web-app-with-ease&quot;&gt;6 Cloud-Based Tools To Help You Build A Web App With Ease&lt;/a&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>So the server crashed</title>
<link href="https://www.0ink.net/posts/2018/2018-05-17-crash.html"></link>
<id>urn:uuid:bc34dc77-3736-3af9-7ec5-9680adae51be</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Now we have to re-create things...
...]]></summary>
<content type="html">&lt;p&gt;Now we have to re-create things...&lt;/p&gt;</content>
</entry>
<entry>
<title>custom desktop ideas</title>
<link href="https://www.0ink.net/posts/2018/2018-04-18-custom-desktop-ideas.html"></link>
<id>urn:uuid:d7ed87f5-639c-d636-4e8d-4e5c0039d2b6</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[
fast boot

Window Manager

Snap windows to border/windows
...]]></summary>
<content type="html">&lt;ul&gt;
&lt;li&gt;fast boot&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Window+Manager&quot; name=&quot;Window+Manager&quot;&gt;Window Manager&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Snap windows to border/windows&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;File+Manager&quot; name=&quot;File+Manager&quot;&gt;File Manager&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://ulf.epplejasper.de/ulfm/ulfm.html&quot;&gt;Next style file manager&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wiki.tcl.tk/7697&quot;&gt;TkDesk&lt;/a&gt; : Note, it use [incr tcl]&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wiki.tcl.tk/7772&quot;&gt;TkMC&lt;/a&gt; : Basic file browser functionality&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wiki.tcl.tk/16045&quot;&gt;TOXFile&lt;/a&gt;: Another file manager&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://jsish.org/browsex/download/&quot;&gt;TkWm&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Notification+and+Tray+areas&quot; name=&quot;Notification+and+Tray+areas&quot;&gt;Notification and Tray areas&lt;/h3&gt;
&lt;h3 id=&quot;Launcher&quot; name=&quot;Launcher&quot;&gt;Launcher&lt;/h3&gt;
&lt;h3 id=&quot;Applications&quot; name=&quot;Applications&quot;&gt;Applications&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;browser&lt;/li&gt;
&lt;li&gt;media player&lt;/li&gt;
&lt;li&gt;photo/video manager (or webapp)&lt;/li&gt;
&lt;li&gt;office apps (open365.io)&lt;/li&gt;
&lt;li&gt;ah photo album app&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://launchpad.net/ubuntu/+source/tkpaint&quot;&gt;tkpaint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://web.tiscali.it/pas80/retrolook.htm&quot;&gt;retrolook&lt;/a&gt; is a Window Manager&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;TCL%2FTK&quot; name=&quot;TCL%2FTK&quot;&gt;TCL/TK&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.wjduquette.com/tcl/objects.html&quot;&gt;objects&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://blog.cleverly.com/permalinks/264.html&quot;&gt;tk&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wiki.tcl.tk/_repo/Whim-2399.tar.bz2&quot;&gt;whim tarball&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/wmutils/libwm&quot;&gt;libwm&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Alt-desktop&quot; name=&quot;Alt-desktop&quot;&gt;Alt-desktop&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Use assist to do a basic install&lt;/li&gt;
&lt;li&gt;Set-up xorg&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;pacman -S xorg-server xorg-server-utils xorg-xinit xorg-utils mesa
xterm xorg-twm xorg-xclock&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Drivers&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;nvidia&lt;/li&gt;
&lt;li&gt;xf86-video-ati&lt;/li&gt;
&lt;li&gt;xf86-video-intel&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Common&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;xf86-input-synaptics : for laptops&lt;/li&gt;
&lt;li&gt;ttf-dejavu ttf-droid ttf-freefont ttf-liberation ttf-opensans&lt;/li&gt;
&lt;li&gt;&lt;em&gt;aur&lt;/em&gt; ttf-ms-fonts &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;budgie: budgie-desktop&lt;/p&gt;</content>
</entry>
<entry>
<title>Retropie</title>
<link href="https://www.0ink.net/posts/2018/2018-03-01-retropie-notes.html"></link>
<id>urn:uuid:367b595a-56c7-abe6-2315-bb1e4f0e0098</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[

DVD player


Bluetooth receiver 
...]]></summary>
<content type="html">&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;DVD player&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Bluetooth receiver &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;keyboard &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;good keyboard bindings&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;how to exit games&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;convert probox into binding keys&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;where are key codes saved&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;ul&gt;
&lt;li&gt;Write image:
&lt;ul&gt;
&lt;li&gt;gunzip &amp;lt; retropie-4.3-rpi2_rpi3.img.gz | sudo dd of=/dev/sde bs=4M&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Configure config.txt
&lt;ul&gt;
&lt;li&gt;hdmi_force_hotplug=1&lt;/li&gt;
&lt;li&gt;hdmi_drive=2&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Boot and configure keyboard
&lt;ul&gt;
&lt;li&gt;D-Pad =&amp;gt; D-Pad&lt;/li&gt;
&lt;li&gt;start : mike (F5)&lt;/li&gt;
&lt;li&gt;select : menu key&lt;/li&gt;
&lt;li&gt;A :  OK (enter)&lt;/li&gt;
&lt;li&gt;B : back&lt;/li&gt;
&lt;li&gt;X : vol-&lt;/li&gt;
&lt;li&gt;Y : vol+
... skip all ...&lt;/li&gt;
&lt;li&gt;HotKey : power&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Configure SSH
&lt;ul&gt;
&lt;li&gt;sudo raspi-config
&lt;ul&gt;
&lt;li&gt;Interfacing Options&lt;/li&gt;
&lt;li&gt;Enable SSH&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Install Kodi
&lt;ul&gt;
&lt;li&gt;?&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;ul&gt;
&lt;li&gt;NFS
&lt;ul&gt;
&lt;li&gt;nfs-common should be installed.&lt;/li&gt;
&lt;li&gt;sudo vi /etc/default/nfs-common&lt;/li&gt;
&lt;li&gt;activate statd&lt;/li&gt;
&lt;li&gt;Add systemctl start rpcbind|nfs-common to /etc/rc.local&lt;/li&gt;
&lt;li&gt;Doing systemctl enable resulted in a messed up system...&lt;/li&gt;
&lt;li&gt;sudo apt-get install autofs&lt;/li&gt;
&lt;li&gt;Enable /net hosts from /etc/auto.master ... pointing to /etc/auto.net
&lt;ul&gt;
&lt;li&gt;alvm1-xvdb1d -&amp;gt; /net/alvm1/media/xvdb1/d&lt;/li&gt;
&lt;li&gt;alvm1-xvdb1m -&amp;gt; /net/alvm1/media/xvdb1/m&lt;/li&gt;
&lt;li&gt;alvm1-xvdb1p -&amp;gt; /net/alvm1/media/xvdb1/p&lt;/li&gt;
&lt;li&gt;alvm1-xvdc1p -&amp;gt; /net/alvm1/media/xvdc1/p&lt;/li&gt;
&lt;li&gt;alvm1-xvdd1p -&amp;gt; /net/alvm1/media/xvdd1/p&lt;/li&gt;
&lt;li&gt;ow1-v1 -&amp;gt; /net/ow1/data/v1&lt;/li&gt;
&lt;li&gt;vs1-xvdb1 -&amp;gt; /net/vs1/media/xvdb1&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://afterthoughtsoftware.com/products/rasclock&quot;&gt;RTC&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;sudo raspi-config
&lt;ul&gt;
&lt;li&gt;Interfacing options&lt;/li&gt;
&lt;li&gt;I2C&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Edit /boot/config.txt
&lt;ul&gt;
&lt;li&gt;dtoverlay=i2c-rtc,pcf2127&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mausberry-circuits.myshopify.com/pages/setup&quot;&gt;Power control:mausberry&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;git clone &lt;a href=&quot;https://github.com/t-richards/mausberry-switch.git&quot;&gt;https://github.com/t-richards/mausberry-switch.git&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;autoreconf -i -f&lt;/li&gt;
&lt;li&gt;./configure&lt;/li&gt;
&lt;li&gt;make&lt;/li&gt;
&lt;li&gt;sudo make install&lt;/li&gt;
&lt;li&gt;Add also mausberry-switch &amp;amp; to /etc/rc.local&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/dillbyrne/es-cec-input&quot;&gt;CEC control&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;sudo apt-get install cec-utils&lt;/li&gt;
&lt;li&gt;sudo apt-get install python-pip&lt;/li&gt;
&lt;li&gt;sudo pip install python-uinput&lt;/li&gt;
&lt;li&gt;git clone &lt;a href=&quot;https://github.com/dillbyrne/es-cec-input.git&quot;&gt;https://github.com/dillbyrne/es-cec-input.git&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;input
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/MerlijnWajer/uinput-mapper&quot;&gt;uinput-mapper&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://blog.pi3g.com/2014/03/uinput-mapper-redirecting-keyboard-and-mouse-to-any-linux-system-using-a-raspberry-pi/&quot;&gt;redirection HID using uinput-mapper&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.raspberrypi.org/forums/viewtopic.php?t=85299&quot;&gt;forums&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://tjjr.fi/sw/python-uinput/&quot;&gt;python-uinput&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://python-evdev.readthedocs.io/en/latest/tutorial.html#&quot;&gt;python evdev&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/pyusb/pyusb&quot;&gt;pytusb&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://mausberry-circuits.myshopify.com/pages/setup&quot;&gt;mausberry setup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/RetroPie/RetroPie-Setup/wiki/Running-ROMs-from-a-Network-Share&quot;&gt;running ROMS from a share&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;ul&gt;
&lt;li&gt;cron backup kodi&lt;/li&gt;
&lt;li&gt;document cec input&lt;/li&gt;
&lt;li&gt;re-do keyboard bindings&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>using cachefiles on an Linux NFS share</title>
<link href="https://www.0ink.net/posts/2018/2018-01-30-cache-nfs-files.html"></link>
<id>urn:uuid:57bd55f9-99bd-5693-b744-4095f81033c0</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[If you often mount and access a remote NFS share on your system, you
will probably want to know how to improve NFS file access performance.
One possibility is using file caching. In Linux, there is a caching
filesystem called FS-Cache which enables file caching for network file
systems such as NFS. FS-Cache is built into the Linux kernel 2.6.30
and higher.
...]]></summary>
<content type="html">&lt;p&gt;If you often mount and access a remote NFS share on your system, you
will probably want to know how to improve NFS file access performance.
One possibility is using file caching. In Linux, there is a caching
filesystem called FS-Cache which enables file caching for network file
systems such as NFS. FS-Cache is built into the Linux kernel 2.6.30
and higher.&lt;/p&gt;
&lt;p&gt;In order for FS-Cache to operate, it needs cache back-end which
provides actual storage for caching. One such cache back-end is
cachefiles. Therefore, once you set up cachefiles, it will
automatically enable file caching for NFS shares.&lt;/p&gt;
&lt;p&gt;In this tutorial, I will describe &lt;strong&gt;how to enable local file caching for
NFS shares&lt;/strong&gt; by using cachefiles.&lt;/p&gt;
&lt;h2 id=&quot;Requirements+for+Setting+Up+CacheFiles&quot; name=&quot;Requirements+for+Setting+Up+CacheFiles&quot;&gt;Requirements for Setting Up CacheFiles&lt;/h2&gt;
&lt;p&gt;One requirement for setting up cachefiles is that local filesystem
support user-defined extended file attributes (i.e., xattr), because
cachefiles use xattr to store extra information for cache maintenance.&lt;/p&gt;
&lt;p&gt;If your local filesystem is ext4-type, you don&#039;t need to worry about
this since xattr is enabled in ext4 by default.&lt;/p&gt;
&lt;p&gt;However, if you are using ext3 filesystem, then you need to mount the
local filesystem with &amp;quot;user_xattr&amp;quot; option. To do so, edit /etc/mtab
to add &amp;quot;user_xattr&amp;quot; mount option to the disk partition that will be
used by cachefiles for file caching. For example, assuming that
&lt;code&gt;/dev/hda1&lt;/code&gt; is such a partition:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/dev/hda1 / ext3 rw,user_xattr  0 0&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After modifying /etc/fstab, reload it by running:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo mount -o remount /&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Set+Up+CacheFiles&quot; name=&quot;Set+Up+CacheFiles&quot;&gt;Set Up CacheFiles&lt;/h2&gt;
&lt;p&gt;In order to set up cache back-end using cachefiles, you need to
install &lt;code&gt;cachefilesd&lt;/code&gt;, a userspace daemon for managing cachefiles.&lt;/p&gt;
&lt;p&gt;To install &lt;code&gt;cachefilesd&lt;/code&gt; on Ubuntu or Debian:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo apt-get install cachefilesd&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To install cachefilesd on CentOS, Fedora or RedHat:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo yum install cachefilesd
sudo chkconfig cachefilesd on&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After installation, enable cachefilesd by editing its configuration
file as follows.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo vi /etc/default/cachefilesd

RUN=yes&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, mount a remote NFS share with fsc option:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo vi /etc/fstab

192.168.1.13:/home/xmodulo /mnt nfs rw,hard,intr,fsc&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alternatively, if you mount the remote NFS share from the command line, specify fsc as a command-line option:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo mount -t nfs 192.168.1.13:/home/xmodulo /mnt -o fsc&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, restart cachefilesd:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo service cachefilesd restart&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point, file caching should be enabled for the mounted NFS
share, which means that previously accessed files in the mounted
NFS share will be retrieved from local file cache.&lt;/p&gt;
&lt;p&gt;If you want to flush NFS file cache for any reason, simply restart
cachefilesd.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo service cachefilesd restart&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reference: &lt;a href=&quot;http://xmodulo.com/how-to-enable-local-file-caching-for-nfs-share-on-linux.html&quot;&gt;xmodule.com&lt;/a&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>VNC desktop</title>
<link href="https://www.0ink.net/posts/2017/2017-11-12-vnc-desktop-client.html"></link>
<id>urn:uuid:bf7a63f9-ebaa-c670-fe7a-3efd64603a8f</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[IDEA:
Client connects &gt;
        &lt; server sends version string (Use 3.3 only)
Client replies with actual verison string &gt;
        &lt; server sends security type; NONE
Client send ClientInit (shared flag) &gt; 
...]]></summary>
<content type="html">&lt;p&gt;IDEA:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Client connects &amp;gt;
        &amp;lt; server sends version string (Use 3.3 only)
Client replies with actual verison string &amp;gt;
        &amp;lt; server sends security type; NONE
Client send ClientInit (shared flag) &amp;gt; 
        &amp;lt; sever sens ServerInit (server details) WxHxD Name
=== standard stuff ===&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;2+VERSIONS&quot; name=&quot;2+VERSIONS&quot;&gt;2 VERSIONS&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;kiosk
&lt;ul&gt;
&lt;li&gt;unmodified vncviewer connects to a multiplexer screen&lt;/li&gt;
&lt;li&gt;server (in inetd mode) first spawns a Xvnc (in inetd mode) which does a login authentication
and finds an existing desktop or spawns a new one
saves the port and exists&lt;/li&gt;
&lt;li&gt;server then connects to the new desktop port and does the VNC handshake.  Sends client
Desktop change and name change messages&lt;/li&gt;
&lt;li&gt;Forwards everything...&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;command
&lt;ul&gt;
&lt;li&gt;User points command to a server.&lt;/li&gt;
&lt;li&gt;Script selects a new port.&lt;/li&gt;
&lt;li&gt;Ssh to server, look for vnc session, or spawn new one.&lt;/li&gt;
&lt;li&gt;netcat to vnc session.&lt;/li&gt;
&lt;li&gt;Listen to to new port, and netcat to ssh.&lt;/li&gt;
&lt;li&gt;vncviewer to netcat port.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We use only v3.3 because we don&#039;t want to mess with security types.  Security should be handled by SSH tunnel.&lt;/p&gt;
&lt;p&gt;Check if user can login&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://wiki.dovecot.org/AuthDatabase/CheckPassword&quot;&gt;checkpassword&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jonabbey/panda-imap/blob/master/src/osdep/unix/ckp_pam.c&quot;&gt;unix pam&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/svarshavchik/courier/blob/master/courier-authlib/authpam.c&quot;&gt;authpam&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/mozilla-b2g/busybox/blob/master/loginutils/login.c&quot;&gt;busybox login&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/mingodad/citadel/blob/master/citadel/auth.c&quot;&gt;citadel auth&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.linuxdevcenter.com/pub/a/linux/2002/04/04/PamModules.html&quot;&gt;PamModules&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>SimpleNote/Markdown editor</title>
<link href="https://www.0ink.net/posts/2017/2017-10-19-markdown-simplenote-editing.html"></link>
<id>urn:uuid:f98f00d1-4d16-594e-beeb-9aa8ae0bf792</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[We just load SimpleNote as a Desktop WebApp

Create first a basic markdown editor

styling

...]]></summary>
<content type="html">&lt;p&gt;&lt;strong&gt;We just load SimpleNote as a Desktop WebApp&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create first a basic markdown editor
&lt;ul&gt;
&lt;li&gt;styling&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Hack retext
&lt;ul&gt;
&lt;li&gt;add stuff for multiple views&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;nvpy remixed with retext?&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.py2exe.org/&quot;&gt;Wrapper python for Windows&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Another option:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SimpleNote native app + editor&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Search tags from a menu&lt;/li&gt;
&lt;li&gt;Search within document&lt;/li&gt;
&lt;li&gt;Poll website for changes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Or just use: &lt;a href=&quot;https://github.com/lepture/mistune&quot;&gt;mistune&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Basic editors&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.instructables.com/id/Create-a-Simple-Python-Text-Editor/&quot;&gt;simple python text editor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://knowpapa.com/text-editor/&quot;&gt;text-editor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.binpress.com/tutorial/building-a-text-editor-with-pyqt-part-one/143&quot;&gt;text-editor with pyqt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://thelivingpearl.com/2013/07/03/simple-gui-text-editor-in-python/&quot;&gt;gui text editor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://code.activestate.com/recipes/578568-plain-text-editor-in-python/&quot;&gt;python plain text editor&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Syntax highligthing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://pygments.org/&quot;&gt;pygments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://stackoverflow.com/questions/32058760/improve-pygments-syntax-highlighting-speed-for-tkinter-text&quot;&gt;pygments with tkinter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://stackoverflow.com/questions/29688831/pygments-syntax-highlighter-in-python-tkinter-text-widget/30198307#30198307&quot;&gt;pygments with tkinter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://stackoverflow.com/questions/29688831/pygments-syntax-highlighter-in-python-tkinter-text-widget&quot;&gt;pygments with tkinter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://pygments.org/docs/lexers/#&quot;&gt;pygments lexer&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Interesting wordpress plugins</title>
<link href="https://www.0ink.net/posts/2017/2017-10-02-wordpress-plugins.html"></link>
<id>urn:uuid:41e45b60-d0be-72e7-643c-a489f86b6686</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[
restrict categories
access control by category
site member
user access mgr
paid members
...]]></summary>
<content type="html">&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://wordpress.org/plugins/restrict-categories/&quot;&gt;restrict categories&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wordpress.org/plugins/access-control-by-category/&quot;&gt;access control by category&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wordpress.org/plugins/s2member/&quot;&gt;site member&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wordpress.org/plugins/user-access-manager/&quot;&gt;user access mgr&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wordpress.org/plugins/paid-memberships-pro/&quot;&gt;paid members&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Rollback with YUM History Command</title>
<link href="https://www.0ink.net/posts/2017/2017-09-21-yum-history-command.html"></link>
<id>urn:uuid:867e4667-edee-1821-605a-b877cd510dce</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[From 2daygeek.com
Server patching is one of the important task of Linux system administrator to make the system more stable and better performance. All the vendors used to release security/vulnerabilities patches very often, the affected package must be updated in order to limit any potential security risks.
Yum (Yellowdog Update Modified) is RPM Package Management utility for CentOS and Red Hat systems, Yum history command allows administrator to rollback the system to a previous state but due to some limitations, rollbacks do not work in all situations, or The yum command may simply do nothing, or it may remove packages you do not expect.
I advise you to take a full system backup prior to performing any update/upgrade is always recommended, and yum history is NOT meant to replace systems backups. This will help you to restore the system to previous state at any point of time.
n some cases, the hosted applications might not work properly or through some error due to recent patch updates (It could be some library incompatibility or package upgrade), what will be the solution in this case?
Get in touch with App Dev team and figure it out an issue creating library' and packages then do the rollback with help of yum history command.
...]]></summary>
<content type="html">&lt;p&gt;From &lt;a href=&quot;https://www.2daygeek.com/rollback-fallback-updates-downgrade-packages-centos-rhel-fedora/&quot;&gt;2daygeek.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Server patching is one of the important task of Linux system administrator to make the system more stable and better performance. All the vendors used to release security/vulnerabilities patches very often, the affected package must be updated in order to limit any potential security risks.&lt;/p&gt;
&lt;p&gt;Yum (Yellowdog Update Modified) is RPM Package Management utility for CentOS and Red Hat systems, Yum history command allows administrator to rollback the system to a previous state but due to some limitations, rollbacks do not work in all situations, or The yum command may simply do nothing, or it may remove packages you do not expect.&lt;/p&gt;
&lt;p&gt;I advise you to take a full system backup prior to performing any update/upgrade is always recommended, and yum history is NOT meant to replace systems backups. This will help you to restore the system to previous state at any point of time.&lt;/p&gt;
&lt;p&gt;n some cases, the hosted applications might not work properly or through some error due to recent patch updates (It could be some library incompatibility or package upgrade), what will be the solution in this case?&lt;/p&gt;
&lt;p&gt;Get in touch with App Dev team and figure it out an issue creating library&#039; and packages then do the rollback with help of yum history command.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Rollback of selinux, selinux-policy-*, kernel, glibc (dependencies of glibc such as gcc) packages to older version is not supported.
Downgrading a system to minor version is not recommended (CentOS 6.9 to CentOS 6.8) which leads to make the system in undesired state
Let&#039; first verify an available updates on system and pick any of the package for this experiment.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# yum update
Loaded plugins: fastestmirror, security
Setting up Update Process
Loading mirror speeds from cached hostfile
epel/metalink                                                             |  12 kB     00:00
 * epel: mirror.csclub.uwaterloo.ca
base                                                                      | 3.7 kB     00:00
dockerrepo                                                                | 2.9 kB     00:00
draios                                                                    | 2.9 kB     00:00
draios/primary_db                                                         |  13 kB     00:00
epel                                                                      | 4.3 kB     00:00
epel/primary_db                                                           | 5.9 MB     00:00
extras                                                                    | 3.4 kB     00:00
updates                                                                   | 3.4 kB     00:00
updates/primary_db                                                        | 2.5 MB     00:00
Resolving Dependencies
--&amp;gt; Running transaction check
---&amp;gt; Package git.x86_64 0:1.7.1-8.el6 will be updated
---&amp;gt; Package git.x86_64 0:1.7.1-9.el6_9 will be an update
---&amp;gt; Package httpd.x86_64 0:2.2.15-60.el6.centos.4 will be updated
---&amp;gt; Package httpd.x86_64 0:2.2.15-60.el6.centos.5 will be an update
---&amp;gt; Package httpd-tools.x86_64 0:2.2.15-60.el6.centos.4 will be updated
---&amp;gt; Package httpd-tools.x86_64 0:2.2.15-60.el6.centos.5 will be an update
---&amp;gt; Package perl-Git.noarch 0:1.7.1-8.el6 will be updated
---&amp;gt; Package perl-Git.noarch 0:1.7.1-9.el6_9 will be an update
--&amp;gt; Finished Dependency Resolution

Dependencies Resolved

=================================================================================================
 Package               Arch             Version                          Repository         Size
=================================================================================================
Updating:
 git                   x86_64           1.7.1-9.el6_9                    updates           4.6 M
 httpd                 x86_64           2.2.15-60.el6.centos.5           updates           836 k
 httpd-tools           x86_64           2.2.15-60.el6.centos.5           updates            80 k
 perl-Git              noarch           1.7.1-9.el6_9                    updates            29 k

Transaction Summary
=================================================================================================
Upgrade       4 Package(s)

Total download size: 5.5 M
Is this ok [y/N]: n&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see in the above output &lt;code&gt;git&lt;/code&gt; package update is available, so we are going to take that. Run the following command to know the version information about the package (current installed version and available update version).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# yum list git
Loaded plugins: fastestmirror, security
Setting up Update Process
Loading mirror speeds from cached hostfile
 * epel: mirror.csclub.uwaterloo.ca
Installed Packages
git.x86_64                                 1.7.1-8.el6                                    @base
Available Packages
git.x86_64                                 1.7.1-9.el6_9                                  updates&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run the following command to update &lt;code&gt;git&lt;/code&gt; package from &lt;code&gt;1.7.1-8&lt;/code&gt; to &lt;code&gt;1.7.1-9&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# yum update git
Loaded plugins: fastestmirror, presto
Setting up Update Process
Loading mirror speeds from cached hostfile
 * base: repos.lax.quadranet.com
 * epel: fedora.mirrors.pair.com
 * extras: mirrors.seas.harvard.edu
 * updates: mirror.sesp.northwestern.edu
Resolving Dependencies
--&amp;gt; Running transaction check
---&amp;gt; Package git.x86_64 0:1.7.1-8.el6 will be updated
--&amp;gt; Processing Dependency: git = 1.7.1-8.el6 for package: perl-Git-1.7.1-8.el6.noarch
---&amp;gt; Package git.x86_64 0:1.7.1-9.el6_9 will be an update
--&amp;gt; Running transaction check
---&amp;gt; Package perl-Git.noarch 0:1.7.1-8.el6 will be updated
---&amp;gt; Package perl-Git.noarch 0:1.7.1-9.el6_9 will be an update
--&amp;gt; Finished Dependency Resolution

Dependencies Resolved

=================================================================================================
 Package               Arch                Version                    Repository            Size
=================================================================================================
Updating:
 git                   x86_64              1.7.1-9.el6_9              updates              4.6 M
Updating for dependencies:
 perl-Git              noarch              1.7.1-9.el6_9              updates               29 k

Transaction Summary
=================================================================================================
Upgrade       2 Package(s)

Total download size: 4.6 M
Is this ok [y/N]: y
Downloading Packages:
Setting up and reading Presto delta metadata
Processing delta metadata
Package(s) data still to download: 4.6 M
(1/2): git-1.7.1-9.el6_9.x86_64.rpm                                       | 4.6 MB     00:00
(2/2): perl-Git-1.7.1-9.el6_9.noarch.rpm                                  |  29 kB     00:00
-------------------------------------------------------------------------------------------------
Total                                                            5.8 MB/s | 4.6 MB     00:00
Running rpm_check_debug
Running Transaction Test
Transaction Test Succeeded
Running Transaction
  Updating   : perl-Git-1.7.1-9.el6_9.noarch                                                 1/4
  Updating   : git-1.7.1-9.el6_9.x86_64                                                      2/4
  Cleanup    : perl-Git-1.7.1-8.el6.noarch                                                   3/4
  Cleanup    : git-1.7.1-8.el6.x86_64                                                        4/4
  Verifying  : git-1.7.1-9.el6_9.x86_64                                                      1/4
  Verifying  : perl-Git-1.7.1-9.el6_9.noarch                                                 2/4
  Verifying  : git-1.7.1-8.el6.x86_64                                                        3/4
  Verifying  : perl-Git-1.7.1-8.el6.noarch                                                   4/4

Updated:
  git.x86_64 0:1.7.1-9.el6_9

Dependency Updated:
  perl-Git.noarch 0:1.7.1-9.el6_9

Complete!&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Verify updated version of &lt;code&gt;git&lt;/code&gt; package.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# yum list git
Installed Packages
git.x86_64                                 1.7.1-9.el6_9                                 @updates

or
# rpm -q git
git-1.7.1-9.el6_9.x86_64&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As of now, we have successfully completed package update and got a package for rollback. Just follow below steps for rollback mechanism.&lt;/p&gt;
&lt;p&gt;First get the yum transaction id using following command. The below output clearly shows all the required information such transaction id, who done the transaction (i mean username), date and time, Actions (Install or update), how many packages altered in this transaction.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# yum history
or
# yum history list all
Loaded plugins: fastestmirror, presto
ID     | Login user               | Date and time    | Action(s)      | Altered
-------------------------------------------------------------------------------
    13 | root               | 2017-08-18 13:30 | Update         |    2
    12 | root               | 2017-08-10 07:46 | Install        |    1
    11 | root               | 2017-07-28 17:10 | E, I, U        |   28 EE
    10 | root               | 2017-04-21 09:16 | E, I, U        |  162 EE
     9 | root               | 2017-02-09 17:09 | E, I, U        |   20 EE
     8 | root               | 2017-02-02 10:45 | Install        |    1
     7 | root               | 2016-12-15 06:48 | Update         |    1
     6 | root               | 2016-12-15 06:43 | Install        |    1
     5 | root               | 2016-12-02 10:28 | E, I, U        |   23 EE
     4 | root               | 2016-10-28 05:37 | E, I, U        |   13 EE
     3 | root               | 2016-10-18 12:53 | Install        |    1
     2 | root               | 2016-09-30 10:28 | E, I, U        |   31 EE
     1 | root               | 2016-07-26 11:40 | E, I, U        |  160 EE&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above command shows two packages has been altered because git updated it&#039; dependence package too &lt;code&gt;perl-Git&lt;/code&gt;. Run the following command to view detailed information about the transaction.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# yum history info 13
Loaded plugins: fastestmirror, presto
Transaction ID : 13
Begin time     : Fri Aug 18 13:30:52 2017
Begin rpmdb    : 420:f5c5f9184f44cf317de64d3a35199e894ad71188
End time       :            13:30:54 2017 (2 seconds)
End rpmdb      : 420:d04a95c25d4526ef87598f0dcaec66d3f99b98d4
User           : root 
Return-Code    : Success
Command Line   : update git
Transaction performed with:
    Installed     rpm-4.8.0-55.el6.x86_64                       @base
    Installed     yum-3.2.29-81.el6.centos.noarch               @base
    Installed     yum-plugin-fastestmirror-1.1.30-40.el6.noarch @base
    Installed     yum-presto-0.6.2-1.el6.noarch                 @anaconda-CentOS-201207061011.x86_64/6.3
Packages Altered:
    Updated git-1.7.1-8.el6.x86_64        @base
    Update      1.7.1-9.el6_9.x86_64      @updates
    Updated perl-Git-1.7.1-8.el6.noarch   @base
    Update           1.7.1-9.el6_9.noarch @updates
history info&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fire the following command to Rollback the &lt;code&gt;git&lt;/code&gt; package to the previous version.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# yum history undo 13
Loaded plugins: fastestmirror, presto
Undoing transaction 53, from Fri Aug 18 13:30:52 2017
    Updated git-1.7.1-8.el6.x86_64        @base
    Update      1.7.1-9.el6_9.x86_64      @updates
    Updated perl-Git-1.7.1-8.el6.noarch   @base
    Update           1.7.1-9.el6_9.noarch @updates
Loading mirror speeds from cached hostfile
 * base: repos.lax.quadranet.com
 * epel: fedora.mirrors.pair.com
 * extras: repo1.dal.innoscale.net
 * updates: mirror.vtti.vt.edu
Resolving Dependencies
--&amp;gt; Running transaction check
---&amp;gt; Package git.x86_64 0:1.7.1-8.el6 will be a downgrade
---&amp;gt; Package git.x86_64 0:1.7.1-9.el6_9 will be erased
---&amp;gt; Package perl-Git.noarch 0:1.7.1-8.el6 will be a downgrade
---&amp;gt; Package perl-Git.noarch 0:1.7.1-9.el6_9 will be erased
--&amp;gt; Finished Dependency Resolution

Dependencies Resolved

=================================================================================================
 Package                Arch                 Version                    Repository          Size
=================================================================================================
Downgrading:
 git                    x86_64               1.7.1-8.el6                base               4.6 M
 perl-Git               noarch               1.7.1-8.el6                base                29 k

Transaction Summary
=================================================================================================
Downgrade     2 Package(s)

Total download size: 4.6 M
Is this ok [y/N]: y
Downloading Packages:
Setting up and reading Presto delta metadata
Processing delta metadata
Package(s) data still to download: 4.6 M
(1/2): git-1.7.1-8.el6.x86_64.rpm                                         | 4.6 MB     00:00
(2/2): perl-Git-1.7.1-8.el6.noarch.rpm                                    |  29 kB     00:00
-------------------------------------------------------------------------------------------------
Total                                                            3.4 MB/s | 4.6 MB     00:01
Running rpm_check_debug
Running Transaction Test
Transaction Test Succeeded
Running Transaction
  Installing : perl-Git-1.7.1-8.el6.noarch                                                   1/4
  Installing : git-1.7.1-8.el6.x86_64                                                        2/4
  Cleanup    : perl-Git-1.7.1-9.el6_9.noarch                                                 3/4
  Cleanup    : git-1.7.1-9.el6_9.x86_64                                                      4/4
  Verifying  : git-1.7.1-8.el6.x86_64                                                        1/4
  Verifying  : perl-Git-1.7.1-8.el6.noarch                                                   2/4
  Verifying  : git-1.7.1-9.el6_9.x86_64                                                      3/4
  Verifying  : perl-Git-1.7.1-9.el6_9.noarch                                                 4/4

Removed:
  git.x86_64 0:1.7.1-9.el6_9                   perl-Git.noarch 0:1.7.1-9.el6_9

Installed:
  git.x86_64 0:1.7.1-8.el6                     perl-Git.noarch 0:1.7.1-8.el6

Complete!&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After rollback, use the following command to re-check the downgraded package version.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# yum list git
or
# rpm -q git
git-1.7.1-8.el6.x86_64&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Rollback+Updates+using+YUM+downgrade+command&quot; name=&quot;Rollback+Updates+using+YUM+downgrade+command&quot;&gt;Rollback Updates using YUM downgrade command&lt;/h3&gt;
&lt;p&gt;Alternatively we can rollback an updates using YUM downgrade command.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# yum downgrade git-1.7.1-8.el6 perl-Git-1.7.1-8.el6
Loaded plugins: search-disabled-repos, security, ulninfo
Setting up Downgrade Process
Resolving Dependencies
--&amp;gt; Running transaction check
---&amp;gt; Package git.x86_64 0:1.7.1-8.el6 will be a downgrade
---&amp;gt; Package git.x86_64 0:1.7.1-9.el6_9 will be erased
---&amp;gt; Package perl-Git.noarch 0:1.7.1-8.el6 will be a downgrade
---&amp;gt; Package perl-Git.noarch 0:1.7.1-9.el6_9 will be erased
--&amp;gt; Finished Dependency Resolution

Dependencies Resolved

=================================================================================================
 Package                Arch                 Version                    Repository          Size
=================================================================================================
Downgrading:
 git                    x86_64               1.7.1-8.el6                base               4.6 M
 perl-Git               noarch               1.7.1-8.el6                base                29 k

Transaction Summary
=================================================================================================
Downgrade     2 Package(s)

Total download size: 4.6 M
Is this ok [y/N]: y
Downloading Packages:
(1/2): git-1.7.1-8.el6.x86_64.rpm                                         | 4.6 MB     00:00
(2/2): perl-Git-1.7.1-8.el6.noarch.rpm                                    |  28 kB     00:00
-------------------------------------------------------------------------------------------------
Total                                                            3.7 MB/s | 4.6 MB     00:01
Running rpm_check_debug
Running Transaction Test
Transaction Test Succeeded
Running Transaction
  Installing : perl-Git-1.7.1-8.el6.noarch                                                   1/4
  Installing : git-1.7.1-8.el6.x86_64                                                        2/4
  Cleanup    : perl-Git-1.7.1-9.el6_9.noarch                                                 3/4
  Cleanup    : git-1.7.1-9.el6_9.x86_64                                                      4/4
  Verifying  : git-1.7.1-8.el6.x86_64                                                        1/4
  Verifying  : perl-Git-1.7.1-8.el6.noarch                                                   2/4
  Verifying  : git-1.7.1-9.el6_9.x86_64                                                      3/4
  Verifying  : perl-Git-1.7.1-9.el6_9.noarch                                                 4/4

Removed:
  git.x86_64 0:1.7.1-9.el6_9                   perl-Git.noarch 0:1.7.1-9.el6_9

Installed:
  git.x86_64 0:1.7.1-8.el6                     perl-Git.noarch 0:1.7.1-8.el6

Complete!&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; : You have to downgrade a dependence packages too, otherwise this will remove the current version of dependency packages instead of downgrade because the downgrade command cannot satisfy the dependency.&lt;/p&gt;
&lt;h3 id=&quot;For+Fedora+Users&quot; name=&quot;For+Fedora+Users&quot;&gt;For Fedora Users&lt;/h3&gt;
&lt;p&gt;Use the same above commands and change the package manager command to DNF instead of YUM.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# dnf list git
# dnf history
# dnf history info
# dnf history undo
# dnf list git
# dnf downgrade git-1.7.1-8.el6 perl-Git-1.7.1-8.el6&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>SeedBoxes</title>
<link href="https://www.0ink.net/posts/2017/2017-09-20-SeedBox.html"></link>
<id>urn:uuid:15866a6d-a5ca-a0c1-aaac-57740d97f459</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[A seedbox is a dedicated server at a high speed datacenter with a
public IP address for the downloading and seeding of bittorrent files.
Persons who have access to a seedbox can download these files to
their personal computers at any time and from any place that has an
internet connection.
References:
...]]></summary>
<content type="html">&lt;p&gt;A seedbox is a dedicated server at a high speed datacenter with a
public IP address for the downloading and seeding of bittorrent files.
Persons who have access to a seedbox can download these files to
their personal computers at any time and from any place that has an
internet connection.&lt;/p&gt;
&lt;p&gt;References:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.rapidseedbox.com/#pricing&quot;&gt;rapidseedbox&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cheapseedboxes.com/top-10-seedbox-best-providers-cheap/&quot;&gt;cheepseedboxes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Centos Install notes</title>
<link href="https://www.0ink.net/posts/2017/2017-07-12-install-centos-notes.html"></link>
<id>urn:uuid:5f7d2603-4ae7-1101-3f12-015f4b3a7be3</id>
<updated>2022-01-04T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Set-up local.repo
yum installs:

nfs-utils autofs
@x11
@xfce
...]]></summary>
<content type="html">&lt;p&gt;Set-up &lt;code&gt;local.repo&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;yum installs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;nfs-utils autofs&lt;/li&gt;
&lt;li&gt;@x11&lt;/li&gt;
&lt;li&gt;@xfce&lt;/li&gt;
&lt;li&gt;wget&lt;/li&gt;
&lt;li&gt;dejavu-sans-fonts dejavu-sans-mono-fonts dejavu-serif-fonts&lt;/li&gt;
&lt;li&gt;xorg-x11-fonts-{Type1,misc,75dpi,100dpi}&lt;/li&gt;
&lt;li&gt;bitmap-console-fonts bitmap-fixed-fonts bitmap-fonts-compat bitmap-lucida-typewriter-fonts&lt;/li&gt;
&lt;li&gt;ucs-miscfixed-fonts urw-fonts&lt;/li&gt;
&lt;li&gt;open-sans-fonts&lt;/li&gt;
&lt;li&gt;webcore-fonts webcore-fonts-vista&lt;/li&gt;
&lt;li&gt;liberation-mono-fonts liberation-sans-fonts liberation-serif-fonts&lt;/li&gt;
&lt;li&gt;bitstream-vera-sans-fonts bitstream-vera-serif-fonts&lt;/li&gt;
&lt;li&gt;gnu-free-{mono,sans,serif}-fonts&lt;/li&gt;
&lt;li&gt;tk&lt;/li&gt;
&lt;li&gt;firefox&lt;/li&gt;
&lt;li&gt;mplayer ffmpeg alsa-utils &lt;/li&gt;
&lt;li&gt;xsensors xfce4-sensors-plugin&lt;/li&gt;
&lt;li&gt;keepassx&lt;/li&gt;
&lt;li&gt;git&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.packagecloud.io/eng/2015/05/11/building-rpm-packages-with-mock/&quot;&gt;Building RPM packages with mock&lt;/a&gt;
and &lt;a href=&quot;https://github.com/perfsonar/project/wiki/CentOS-Mock-Overview&quot;&gt;Centos mock overview&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://fedoraproject.org/wiki/Using_Mock_to_test_package_builds#Building_packages_that_depend_on_packages_not_in_a_repository&quot;&gt;Using mock with fedora&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://blog.packagecloud.io/eng/2015/05/11/building-rpm-packages-with-mock/&quot;&gt;packaging rpms with mock&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/oussemos/cf81d86a446544bfa9c92f3576306aff&quot;&gt;Adding trusted certs…&lt;/a&gt; or &lt;a href=&quot;https://access.redhat.com/solutions/1549003&quot;&gt;rehat solution&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.devdungeon.com/content/how-use-ssl-sockets-php&quot;&gt;php using ssl&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.tecmint.com/install-google-chrome-on-redhat-centos-fedora-linux/&quot;&gt;Chrome on Centos7&lt;/a&gt; from &lt;a href=&quot;https://www.google.com/linuxrepositories/&quot;&gt;google repos&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;travis+ci+installation&quot; name=&quot;travis+ci+installation&quot;&gt;travis ci installation&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Install ruby (must be greater than 1.9.3, 2.0.0 recomended)&lt;/li&gt;
&lt;li&gt;Additional dependencies (through yum)&lt;/li&gt;
&lt;li&gt;ruby-ffi&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As normal user:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;gem install travis -v 1.8.8 --no-rdoc --no-ri&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For a new application...&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;travis enable
travis settings builds_only_with_travis_yml -t&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/proot-me/PRoot/releases&quot;&gt;PRoot&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;project
-module: centos/alpine
-module: proot&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.travis-ci.com/user/customizing-the-build/&quot;&gt;travis ci custom build&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;install: install any dependencies required&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;script: run the build script&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Nameing+conventions&quot; name=&quot;Nameing+conventions&quot;&gt;Nameing conventions&lt;/h2&gt;
&lt;p&gt;Naming tmXXXXYYYYRRRR&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;tmc7r1 : centos 7 template&lt;/li&gt;
&lt;li&gt;tmwin7r1 : not using these, I think it is better to do fresh install...&lt;/li&gt;
&lt;li&gt;tmal3r1 :  alpine linux template. 
&lt;ul&gt;
&lt;li&gt;after boot:
&lt;ol&gt;
&lt;li&gt;mount xvda1 on /media/xvda1 and run setup-alpine&lt;/li&gt;
&lt;li&gt;modify /etc/inittab /etc/securetty to allow ttyS0 (console) login&lt;/li&gt;
&lt;li&gt;may need to switch cdrom as needed.&lt;/li&gt;
&lt;/ol&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sample vm names:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;winvm1 : windows vm&lt;/li&gt;
&lt;li&gt;cvm2 : centos vm&lt;/li&gt;
&lt;li&gt;alvm3 : alpine linux vm&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;More complete guides: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://wiki.alpinelinux.org/wiki/Linux_Router_with_VPN_on_a_Raspberry_Pi&quot;&gt;Rasperry Pi router&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wiki.alpinelinux.org/wiki/How-To_Alpine_Wall&quot;&gt;AWall&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wiki.alpinelinux.org/wiki/Nginx&quot;&gt;webserver + php&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wiki.alpinelinux.org/wiki/Configure_Networking&quot;&gt;Networking&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://egustafson.github.io/ipv6-dhcpv6.html&quot;&gt;DNSMasq IPv6 stuff&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://hveem.no/using-dnsmasq-for-dhcpv6&quot;&gt;Another DNSmasq + ipv6&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Second wifi on OpenWrt&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://cucumberwifi.io/community/tutorials/openwrt-adding-second-ssid.html&quot;&gt;cucumber-wifi&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.smallbusinesstech.net/more-complicated-instructions/openwrt/hosting-two-wifi-networks-on-one-openwrt-router&quot;&gt;smalltech&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>android development</title>
<link href="https://www.0ink.net/posts/2017/2017-06-24-android-dev.html"></link>
<id>urn:uuid:8d4562b7-60e0-79f0-4f0d-ca25d62bc0b6</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Android devs
Install JAVA
yum install java-1.8.0-openjdk java-1.8.0-openjdk-devel
Install SDK Tools:
Download the sdk-tools zip from here
mkdir /opt/android
...]]></summary>
<content type="html">&lt;p&gt;Android devs&lt;/p&gt;
&lt;h2 id=&quot;Install+JAVA&quot; name=&quot;Install+JAVA&quot;&gt;Install JAVA&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;yum install java-1.8.0-openjdk java-1.8.0-openjdk-devel&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Install+SDK+Tools%3A&quot; name=&quot;Install+SDK+Tools%3A&quot;&gt;Install SDK Tools:&lt;/h2&gt;
&lt;p&gt;Download the sdk-tools zip from &lt;a href=&quot;https://developer.android.com/studio/index.html#download&quot;&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir /opt/android
cd /opt/android&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The sdk should go under &lt;code&gt;/opt/android/tools&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;unzip sdk-tools.zip
sudo chmod a+x $(sudo find . -type f -executable )&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a &lt;code&gt;/etc/profile.d&lt;/code&gt; or just modify &lt;code&gt;PATH&lt;/code&gt; directly&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ANDROID_HOME=/opt/android
$ANDROID_HOME/tools/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/tools&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo /opt/android/tools/bin/sdkmanager tools&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This refreshes the tools and makes sure some basic stuff is there.&lt;/p&gt;
&lt;h2 id=&quot;SDK+Manager+packages&quot; name=&quot;SDK+Manager+packages&quot;&gt;SDK Manager packages&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sdkmanager --list&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;TO check the list and install (use sudo...)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;build-tools;&amp;lt;version&amp;gt;
platforms;android-&amp;lt;version&amp;gt;
 &#039;system-images;android-19;google_apis;x86&#039;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Install latest, and others as required&lt;/p&gt;
&lt;p&gt;This is needed by cordova and some applications:&lt;/p&gt;
&lt;h3 id=&quot;Gradle&quot; name=&quot;Gradle&quot;&gt;Gradle&lt;/h3&gt;
&lt;p&gt;Choose binary only &lt;a href=&quot;https://gradle.org/releases&quot;&gt;releases&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd /opt
unzip gradle-bin.zip
ln -s gradle-&amp;lt;version&amp;gt; gradle&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add gradle bin to &lt;code&gt;PATH&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;When creating AVDs need to tweak config.ini (inside $HOME/.android/avd/&lt;name&gt;.avd&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hw.gpu.enabled=yes
hw.gpu.mode=host&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;CORDOVA+INSTALL&quot; name=&quot;CORDOVA+INSTALL&quot;&gt;CORDOVA INSTALL&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sudo yum install nodejs npm (From EPEL)
sudo npm install -g cordova
sudo npm install -g typescript (Optional)&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>Securing rsync on ssh</title>
<link href="https://www.0ink.net/posts/2017/2017-06-01-Securing-rsync-on-ssh.html"></link>
<id>urn:uuid:eebc6591-3a4d-0d2a-408c-9f4f055c74bb</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Reference: positon.org
You have 2 systems and you want to set up a secure backup with rsync + SSH of one system to the other.
Very simply, you can use:
backup.example.com# rsync -avz --numeric-ids --delete root@myserver.example.com:/path/ /backup/myserver/
To do the backup, you have to be root on the remote server, because some files are only root readable.
Problem: you will allow backup.example.com to do anything on myserver.example.com, where just read only access on the directory is sufficient.
...]]></summary>
<content type="html">&lt;p&gt;Reference: &lt;a href=&quot;http://positon.org/rsync-command-restriction-over-ssh&quot;&gt;positon.org&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You have 2 systems and you want to set up a secure backup with rsync + SSH of one system to the other.&lt;/p&gt;
&lt;p&gt;Very simply, you can use:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;backup.example.com# rsync -avz --numeric-ids --delete root@myserver.example.com:/path/ /backup/myserver/&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To do the backup, you have to be root on the remote server, because some files are only root readable.&lt;/p&gt;
&lt;p&gt;Problem: you will allow backup.example.com to do anything on myserver.example.com, where just read only access on the directory is sufficient.&lt;/p&gt;
&lt;p&gt;To solve it, you can use the command=&amp;quot;&amp;quot; directive in the authorized_keys file to filter the command.&lt;/p&gt;
&lt;p&gt;To find this command, start rsync adding the -e&#039;ssh -v&#039; option:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rsync -avz -e&#039;ssh -v&#039; --numeric-ids --delete root@myserver.example.com:/path/ /backup/myserver/ 2&amp;gt;&amp;amp;1 | grep &quot;Sending command&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You get a result like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;debug1: Sending command: rsync --server --sender -vlogDtprze.iLsf --numeric-ids . /path/&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, just add the command before the key in /root/.ssh/authorized_keys:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;command=&quot;rsync --server --sender -vlogDtprze.iLsf --numeric-ids . /path/&quot; ssh-rsa AAAAB3NzaC1in2EAAAABIwAAABio......&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And for even more security, you can add an IP filter, and other options:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from=&quot;backup.example.com&quot;,command=&quot;rsync --server --sender -vlogDtprze.iLsf --numeric-ids . /path/&quot;,no-agent-forwarding,no-port-forwarding,no-pty,no-user-rc,no-X11-forwarding ssh-rsa AAAAB3NzaC1in2EAAAABIwAAABio......&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now try to open a ssh shell on the remote server.. and try some unauthorized rsync commands...
&lt;em&gt;Notes:&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Beware that if you change rsync command options, change also the authorized_keys file.&lt;/li&gt;
&lt;li&gt;No need for complex chroot anymore.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See also:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;man ssh #/AUTHORIZED_KEYS FILE FORMAT&lt;/li&gt;
&lt;li&gt;man rsync&lt;/li&gt;
&lt;li&gt;view /usr/share/doc/rsync/scripts/rrsync.gz (restricted rsync, allows you to manage allowed options precisely)&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Desktop environments on Centos 7</title>
<link href="https://www.0ink.net/posts/2017/2017-04-18-desktop-environments-on-centos-7.html"></link>
<id>urn:uuid:e931b051-af56-147a-9f62-8ff0673beb14</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[These are commands to install different Desktop environments
on Centos7
Gnome
yum groupinstall 'GNOME Desktop'
KDE
yum groupinstall "KDE Plasma Workspaces"
...]]></summary>
<content type="html">&lt;p&gt;These are commands to install different Desktop environments
on Centos7&lt;/p&gt;
&lt;h3 id=&quot;Gnome&quot; name=&quot;Gnome&quot;&gt;Gnome&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;yum groupinstall &#039;GNOME Desktop&#039;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;KDE&quot; name=&quot;KDE&quot;&gt;KDE&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;yum groupinstall &quot;KDE Plasma Workspaces&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Cinnamon&quot; name=&quot;Cinnamon&quot;&gt;Cinnamon&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;yum install epel-release
yum --enablerepo=epel install cinnamon&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;MATE&quot; name=&quot;MATE&quot;&gt;MATE&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;yum install epel-release
yum --enablerepo=epel groupinstall &quot;MATE Desktop&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;XFCE&quot; name=&quot;XFCE&quot;&gt;XFCE&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;yum install epel-release
yum --enablerepo=epel groupinstall XFCE&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>Telegram</title>
<link href="https://www.0ink.net/posts/2017/2017-04-12-Telegram.html"></link>
<id>urn:uuid:318da1c6-832b-f1fb-6c9a-c85a0617da8d</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Telegram is a messenger designed to overcome
the limitations of other messengers like WhatsApp or similar ones. It
is different and better than other messengers on more than one level.
A few of the important features that make it stand out among other
messengers are:

...]]></summary>
<content type="html">&lt;p&gt;&lt;a href=&quot;https://telegram.org/&quot;&gt;Telegram&lt;/a&gt; is a messenger designed to overcome
the limitations of other messengers like WhatsApp or similar ones. It
is different and better than other messengers on more than one level.
A few of the important features that make it stand out among other
messengers are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Open API&lt;/em&gt;. This enables the users to develop their own versions of
the telegram.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Open Protocol&lt;/em&gt;. This protocol is highly secure and will protect
important messages from hackers&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Free&lt;/em&gt;. As is freedom as well as subscription. There will be no ads
and the developers intend to keep it that way forever.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;It has unlimited cloud storage&lt;/em&gt;. There is no limit on the size of
media or chats.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Available on all devices like tablets, smartphones, desktop ( Mac,
Windows, and Linux as well) and there is web version too...
Telegram is not here for profits but instead to provide users an
alternative to all those other messengers which don&#039; value privacy. We
will now look at setting up of the desktop telegram on Linux. &lt;/p&gt;
&lt;p&gt;I used arch with deepin desktop, but telegram should work in any Linux
based PC.&lt;/p&gt;
&lt;p&gt;First, Visit the telegram website and choose telegram for PC/Mac/Linux.&lt;/p&gt;
&lt;p&gt;Then, the website should automatically detect your Linux and show you
the download link. Or you can select the one that suits your PC. Keep
in mind the CPU architecture 32 bit or 64 bit. Click on Download.&lt;/p&gt;
&lt;p&gt;From &lt;a href=&quot;http://www.linuxandubuntu.com/home/telegram-messenger-on-linux-telegram-linux&quot;&gt;linuxandubuntu&lt;/a&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>Free Clipart sites</title>
<link href="https://www.0ink.net/posts/2017/2017-04-11-free-clipart-sites.html"></link>
<id>urn:uuid:75b18339-bc91-1c3e-e1aa-c85a12fb1d72</id>
<updated>2022-01-10T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[In 2014, Microsoft killed and buried Clipart
in the digital graveyard.
Clipart had outlived its usefulness as users relied more on search
engines than Microsoft' somewhat limited supply through the Office
suite.
Today' clipart needs to be modern, colorful, and less cartoonish. An
...]]></summary>
<content type="html">&lt;p&gt;In 2014, Microsoft &lt;a href=&quot;https://www.makeuseof.com/tag/clip-art-gone-heres-find-free-images-instead/&quot;&gt;killed and buried Clipart&lt;/a&gt;
in the digital graveyard.&lt;/p&gt;
&lt;p&gt;Clipart had outlived its usefulness as users relied more on search
engines than Microsoft&#039; somewhat limited supply through the Office
suite.&lt;/p&gt;
&lt;p&gt;Today&#039; clipart needs to be modern, colorful, and less cartoonish. An
online search for clip art images will net you files that are of far
better quality and relevance. But you need shortcuts when searching for
the right image is a daily workout.&lt;/p&gt;
&lt;p&gt;These are &lt;em&gt;13 of the top websites&lt;/em&gt; for free clipart downloads. Browse
through them and bookmark the ones that meet your needs.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.clker.com/&quot;&gt;clker.com&lt;/a&gt; : This site is among the more neatly designed ones you can expect to find for clipart hunting. The images are free to download and use. All images are in the public domain, so feel free to use them anywhere after agreeing to the site&#039; terms of use policy.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.vecteezy.com/free-vector/vector-clipart-free-download&quot;&gt;Vecteezy&lt;/a&gt; : Vecteezy
covers the gamut of vector art -from vector icons to vector patterns. Both free and premium
image files are available for use. You can visit the small section for free vector clipart
downloads which is still choc-full of nearly &lt;em&gt;75000&lt;/em&gt; assets. The community contributions keep the
artwork fresh and updated.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://etc.usf.edu/clipart/&quot;&gt;ClipArt Etc.&lt;/a&gt; : Start with any of the &lt;em&gt;71,500&lt;/em&gt; clip art images on this simple site. The site keeps its focus on classroom friendly images that are appropriate for school websites, class projects, student reports, homework assignments, presentations, posters, art projects, picture books, bulletin boards, and creating teaching aids. For instance, go over to the Fractions collection that has nearly 500 files to help demonstrate math concepts in class.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.webweaver.nu/&quot;&gt;Webweaver&#039; Free Clipart&lt;/a&gt; : Free images and animations without the annoyance of pop ups or registration. That&#039; how this simple site announces itself, and it lives up to its name. Clip art categories include Holidays, Animals &amp;amp; Nature, Celebrations, Historical, and even a dedicated one for Fantasy. No Game of Thrones here, but I spotted a few that could be passed off as Gandalf from LOTR.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.clipartof.com/&quot;&gt;Clipart Of&lt;/a&gt; : Clipart Of is a stock image website offering royalty-free vector, cartoon and 3D files, illustrations and clipart from artists around the world. One glance at the home page tells you that the quality is better than the average. But you have to pay for them.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.artvex.com/&quot;&gt;ArtVex&lt;/a&gt; : The free clip art database gives you more than &lt;em&gt;10,000&lt;/em&gt; original images to choose from. The files are neatly categorized and you can also use the Google custom search engine to go through the vast collection. Some useful categories include Shapes Signs &amp;amp; Symbols, Math, Callouts, and Stickmen &amp;amp; Figures.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.clipartlord.com/&quot;&gt;Clipart Lord&lt;/a&gt; : There&#039; nothing remarkable about the site apart from the fact that someone has gone to the pains of collecting the best clip art found over the web. If you are a clip art buff then the simple site is worth a bookmark. I usually find some excellent files here and use them in my projects.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.vectorportal.com/StockVectors/Clip-art/&quot;&gt;Vector Portal&lt;/a&gt; : The site
creates and showcases free stock vectors which designers can use in commercial
projects. The library includes a good collection of stock vectors and clip art
images which you can use with an attribution. Vector Portal allows you non-exclusive,
non-transferable right to use and modify its images which carry a &amp;quot;or commercial use&amp;quot;  license. Most of the files are in the EPS and AI formats (supported by Adobe Illustrator).&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.freepngimg.com/&quot;&gt;Free PNG IMG&lt;/a&gt; : You can you can download free PNG images, pictures, icons and clip arts in high resolution quality. Categories covered include animals, artistic icons, cars, cartoons, clothing, electronics, games, fantasy, and more. It is effortless to drill down to the one you want with the detailed category breakdown. Some of the icons you might not find on most sites cover learning, internet, and entertainment related themes.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.pdclipart.org/&quot;&gt;PD Clip Art&lt;/a&gt; : Public Domain Clip Art is a growing trove of clipart images which requires no sign-up to download and use.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://all-free-download.com/free-vector/vector-clip-art/&quot;&gt;All Free Download&lt;/a&gt; : More than &lt;em&gt;21000&lt;/em&gt; clip art choices organized in 700 pages should be enough to keep you busy. You don&#039; have to rummage around as all files are organized around tags. Most files are in the Adobe Illustrator (AI) and Encapsulated PostScript (EPS) format. As new files are added daily, sort them by newest first. Any download is free for commercial use with attribution.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://school.discoveryeducation.com/clipart/&quot;&gt;Discovery Education&lt;/a&gt; : Discovery is one of the more &lt;a href=&quot;https://www.makeuseof.com/tag/10-video-websites-kids-safe-fun/&quot;&gt;kid-friendly websites&lt;/a&gt; you can visit on the web. Go straight to the space devoted for clip arts tucked away among their educational videos and curricular resources. The graphics are excellent and cover most of the categories (including animated clip arts) you would need to complete any school assignment. The designs are consistent because they are made by one designer.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://commons.wikimedia.org/wiki/Main_Page&quot;&gt;Wikimedia Commons&lt;/a&gt; : This is arguably the largest collection of free images on the web. So far there are &lt;em&gt;38,205,390&lt;/em&gt; freely usable media files and it is always open for contribution from anyone in the world. Use the search box at the top to ferret the files you want to use. As a regular user, you can take advantage of the syndicated feeds to grab the latest images that come into the archive.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;From &lt;a href=&quot;http://www.makeuseof.com/tag/the-best-websites-for-free-clipart-downloads/&quot;&gt;makeuseof&lt;/a&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>Anti Roboto skills</title>
<link href="https://www.0ink.net/posts/2017/2017-03-30-Anti-Robot-skills.html"></link>
<id>urn:uuid:6fdc7f88-2ad7-82d7-548b-ce30643918ae</id>
<updated>2022-11-02T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Losing your job to robots is no longer a sci-fi fantasy.
Some estimates say, robots may take over more than five million jobs
across 15 developed countries. Machines could account for more than
half the workforce in places like Cambodia and Indonesia, particularly
in the garment industry.
While such information has led many people to seek out higher-tech
...]]></summary>
<content type="html">&lt;p&gt;Losing your job to robots is no longer a sci-fi fantasy.&lt;/p&gt;
&lt;p&gt;Some estimates say, robots may take over more than five million jobs
across 15 developed countries. Machines could account for more than
half the workforce in places like Cambodia and Indonesia, particularly
in the garment industry.&lt;/p&gt;
&lt;p&gt;While such information has led many people to seek out higher-tech
skills, others have said we need a stronger emphasis on trade skills
to combat the high competition in tech fields. In one 2016 survey, 60
percent of respondents wanted more emphasis on Shop classes in high
schools, while a 2015 Gallup poll found that 90 percent of parents
want computer sciences emphasized in schools.&lt;/p&gt;
&lt;p&gt;The good news. There are some skills robots can&#039;t embody, and if you
have them, there&#039;s no need to worry about losing your job due to
robotic advancements. Better yet, many of them are transferrable,
meaning they can help you advance your career, even if you need to
change industries.&lt;/p&gt;
&lt;p&gt;Here are eight skills that can keep your job from being handed off
to a robot.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Complex Problem-Solving Skills&lt;/li&gt;
&lt;li&gt;Project and Personnel Management Skills&lt;/li&gt;
&lt;li&gt;Athletic Skills&lt;/li&gt;
&lt;li&gt;Confidence and Leadership Skills&lt;/li&gt;
&lt;li&gt;Critical Thinking and Judgment Skills&lt;/li&gt;
&lt;li&gt;Empathy Skills&lt;/li&gt;
&lt;li&gt;Listening Skills&lt;/li&gt;
&lt;li&gt;Robotics and Hardware Repair&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Source: &lt;a href=&quot;http://www.makeuseof.com/tag/job-skills-robot/&quot;&gt;makeuseof&lt;/a&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>stop procrastinating</title>
<link href="https://www.0ink.net/posts/2017/2017-03-24-stop-procrastinating.html"></link>
<id>urn:uuid:a2d77deb-f1d6-56a6-e348-f6ba95afeab5</id>
<updated>2022-11-02T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[We are all guilty of procrastinating from time to time--here's always
something more interesting than the work in hand. We usually think it's
no big deal, since deadline is our biggest inspiration, and we do our
best work when we're inspired. We may even joke about it.
However, procrastination is a massive waste of time as it turns out.
A survey in 2015 found that on average, a person loses over 55 days
...]]></summary>
<content type="html">&lt;p&gt;We are all guilty of procrastinating from time to time--here&#039;s always
something more interesting than the work in hand. We usually think it&#039;s
no big deal, since deadline is our biggest inspiration, and we do our
best work when we&#039;re inspired. We may even joke about it.&lt;/p&gt;
&lt;p&gt;However, procrastination is a massive waste of time as it turns out.&lt;/p&gt;
&lt;p&gt;A survey in 2015 found that &lt;strong&gt;on average, a person loses over 55 days
per year&lt;/strong&gt; procrastinating, wasting around 218 minutes every day on
doing unimportant things.&lt;/p&gt;
&lt;p&gt;Here&#039; the maths:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;218 minutes/day x 365 = 79570 minutes = 55.3 days&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;That&#039; a lot of time wasted!&lt;/p&gt;
&lt;p&gt;If you think you need to have a lot of willpower to get productive,
you&#039;re wrong.&lt;/p&gt;
&lt;p&gt;We&#039;re human beings, we all have limited willpower. Our brain is wired
to instant gratification. Temporary rewards are always more tempting
to us.&lt;/p&gt;
&lt;p&gt;When you make plans, you&#039;re making plans for your future self.
You&#039;ll only experience the benefits in future. But most of the time,
the present moment can give you the immediate reward you want, making
you want to delay the plans and just enjoy the moment.&lt;/p&gt;
&lt;p&gt;This is why relying on our willpower to stop procrastination will never
be effective. What we should do is to look into the root causes of
procrastination and start with the small things we can do every day
and build a habit of staying productive.&lt;/p&gt;
&lt;p&gt;Basically there&#039;e 5 common reasons why we procrastinate.&lt;/p&gt;
&lt;p&gt;Identify the real reason and find out how to stop procrastination
accordingly:&lt;/p&gt;
&lt;h3 id=&quot;Type+1%3A+The+Perfectionist&quot; name=&quot;Type+1%3A+The+Perfectionist&quot;&gt;Type 1: The Perfectionist&lt;/h3&gt;
&lt;p&gt;They are the ones who pay too much attention to the minor details.
The perfectionist is afraid to start a task because they get stressed
out about getting every detail right. They can also get stuck in the
process even when they&#039;ve started since they&#039;re just too scared to move
on.&lt;/p&gt;
&lt;h4 id=&quot;Advice+for+the+Perfectionist%3A&quot; name=&quot;Advice+for+the+Perfectionist%3A&quot;&gt;Advice for the Perfectionist:&lt;/h4&gt;
&lt;p&gt;Instead of letting your obsession with details take up all your time,
be clear about the purpose of your tasks and assign a time limit to
each task. This will force you to stay focused and finish your task
within the time frame.&lt;/p&gt;
&lt;p&gt;For example,&lt;/p&gt;
&lt;p&gt;If you&#039;re going to write a report, be clear about the purpose of the
report first.&lt;/p&gt;
&lt;p&gt;If the goal of having the report is to clearly present the changes
in data over the past few months, don&#039;t sweat too much about writing
up a lot of dainty words; rather, focus more on the figures and
charts. Just make sure the goal can be reached, and there&#039;s really
no need to work on things that don&#039;t help you achieve the ultimate goal.&lt;/p&gt;
&lt;h3 id=&quot;Type+2%3A+The+Dreamer&quot; name=&quot;Type+2%3A+The+Dreamer&quot;&gt;Type 2: The Dreamer&lt;/h3&gt;
&lt;p&gt;This is someone who enjoys making the ideal plan more than taking
actions. They are highly creative, but find it hard to actually finish
a task.&lt;/p&gt;
&lt;h4 id=&quot;Advice+for+the+Dreamer&quot; name=&quot;Advice+for+the+Dreamer&quot;&gt;Advice for the Dreamer&lt;/h4&gt;
&lt;p&gt;To stop yourself from being carried away by your endless imagination,
get your feet back on the ground by setting specific (and achievable)
goals for each day based on the SMART framework. Set a goal and break
down the plan into small tasks that you can take actions right away.&lt;/p&gt;
&lt;p&gt;For example,&lt;/p&gt;
&lt;p&gt;If you dream about waking up earlier every day, set a clear goal
about it - &amp;quot;In 3 weeks, I will wake up at 6:30am every day.&amp;quot;&lt;/p&gt;
&lt;p&gt;Then, break this goal down into smaller tasks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;From tonight onwards, I will go to sleep before 11:00pm.&lt;/li&gt;
&lt;li&gt;Set alarm to remind me to go to sleep&lt;/li&gt;
&lt;li&gt;Schedule earlier friends gathering so I can go to sleep early&lt;/li&gt;
&lt;li&gt;For the 1st week, I will wake up at 7:30am even for non-working days&lt;/li&gt;
&lt;li&gt;Go jogging or swimming in the morning for weekends&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;... and the task list goes on.&lt;/p&gt;
&lt;p&gt;Also, you should reflect on your progress while you work. Track your
input and output for each task, so you can easily tell which tasks are
only a waste of time with little importance.6 This can help you focus
on doing the things that bring positive results, which will improve
productivity.&lt;/p&gt;
&lt;h3 id=&quot;Type+3%3A+The+Avoider&quot; name=&quot;Type+3%3A+The+Avoider&quot;&gt;Type 3: The Avoider&lt;/h3&gt;
&lt;p&gt;The worrier are scared to take on tasks that they think they can&#039;t
manage. They would rather put off work than be judged by others when
they end up making mistakes.&lt;/p&gt;
&lt;h4 id=&quot;Advice+for+the+Avoider&quot; name=&quot;Advice+for+the+Avoider&quot;&gt;Advice for the Avoider&lt;/h4&gt;
&lt;p&gt;I know checking emails seems tempting, but don&#039;t make answering
emails the first thing on your to-do list. More often than not,
emails are unimportant. But they steal your time and mental energy
before you even notice.&lt;/p&gt;
&lt;p&gt;Instead, focus on the worst first.  Spend your morning working on
what you find the most challenging. This will give you a sense of
achievement, and helps you build momentum for a productive day ahead.&lt;/p&gt;
&lt;p&gt;Try to break down your tasks into smaller sub-tasks. Understand how
much time and energy is really needed for a given task. Make realistic
calculations.&lt;/p&gt;
&lt;p&gt;For example,&lt;/p&gt;
&lt;p&gt;A 2000-word report does seem to take a lot of time and effort, it
does seem scary to just start working on it. But is there anyway to
break this down into smaller pieces so it&#039;ll seem less scary?
What about this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Introduction: around 100 words (15 min)&lt;/li&gt;
&lt;li&gt;Table of content (5 min)&lt;/li&gt;
&lt;li&gt;Report on the financial status: a chart with 100 supporting text (20 min)&lt;/li&gt;
&lt;li&gt;Case study: 3 cases based on the new business model with around 400 words each (around 40 min each)&lt;/li&gt;
&lt;li&gt;Conclusion: around 800 words (30 min)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Does it look a lot more easier now?&lt;/p&gt;
&lt;h3 id=&quot;Type+4%3A+The+Crisis-maker&quot; name=&quot;Type+4%3A+The+Crisis-maker&quot;&gt;Type 4: The Crisis-maker&lt;/h3&gt;
&lt;p&gt;Now the crisis-maker deliberately pushes back work until the last
minute. They find deadlines (the crises) exciting, and believe that
they work best when being forced to rush it.&lt;/p&gt;
&lt;h4 id=&quot;Advice+for+the+Crisis-maker&quot; name=&quot;Advice+for+the+Crisis-maker&quot;&gt;Advice for the Crisis-maker&lt;/h4&gt;
&lt;p&gt;Being forced to rush the work will perform better is just an illusion
because it actually leaves you no room for reviewing the work to make
it better afterwards.&lt;/p&gt;
&lt;p&gt;If you always leave work until the last minute, try using the
Pomodoro technique. Literally the &amp;quot;tomato technique&amp;quot; developed by
Italian entrepreneur Francesco Cirillo.&lt;/p&gt;
&lt;p&gt;It focuses on working in short, intensely focused bursts, and then
giving yourself a brief break to recover and start over.&lt;/p&gt;
&lt;p&gt;For example,&lt;/p&gt;
&lt;p&gt;Use a timer and divide your complex work into small manageable
sessions. In between the small sessions, give yourself a break
to recover.&lt;/p&gt;
&lt;p&gt;While giving your brain a regular break can highly boost your
performance by recharging your brain&#039;s energy;10 having completed
the tasks earlier allows you to have plenty of time to go through
your work again to make it even better.&lt;/p&gt;
&lt;h3 id=&quot;Type+5%3A+The+Busy+Procrastinator&quot; name=&quot;Type+5%3A+The+Busy+Procrastinator&quot;&gt;Type 5: The Busy Procrastinator&lt;/h3&gt;
&lt;p&gt;This type of procrastinators are the fussy ones. They have trouble
prioritizing tasks because they either have too many of them or
refuse to work on what they see as unworthy of their effort.
They don&#039;t know how to choose the task that&#039;s best for them and
simply postpone making any decisions.&lt;/p&gt;
&lt;h4 id=&quot;Advice+for+the+Busy+Procrastinator&quot; name=&quot;Advice+for+the+Busy+Procrastinator&quot;&gt;Advice for the Busy Procrastinator&lt;/h4&gt;
&lt;p&gt;You have to get your priorities straight. Important tasks should
take priority over urgent ones because &amp;quot;urgent&amp;quot; doesn&#039;t always mean
important. You only have so much time and energy, and you don&#039;t want
to waste that on things that don&#039; matter.&lt;/p&gt;
&lt;p&gt;Identify the purpose of your task and the expected outcome. Important
tasks are the ones that add value in the long run.&lt;/p&gt;
&lt;p&gt;Replying an email that&#039;s written &amp;quot;please get back to me asap&amp;quot; seems
to be urgent, but before you reply that email, think about how
important it is compared to other tasks.&lt;/p&gt;
&lt;p&gt;For example,&lt;/p&gt;
&lt;p&gt;Imagine the email is sent by a client asking about the progress of
a project and she wants you to reply her as soon as possible; at the
same time you have another task about fixing the logistics problem
that is affecting all the projects on hand. Which one should you
handle first?&lt;/p&gt;
&lt;p&gt;The time cost for replying an email is as low as just around 5
minutes but the benefit is also very low because you&#039;re just
satisfying one client request. Fixing the logistic problem probably
takes a lot more time but it&#039;s also a lot more worth it because by
fixing the problem, you&#039;re saving all the projects on hands,
benefiting the whole company.&lt;/p&gt;
&lt;h3 id=&quot;Be+smart+about+every+small+choice+you+make+because...&quot; name=&quot;Be+smart+about+every+small+choice+you+make+because...&quot;&gt;Be smart about every small choice you make because...&lt;/h3&gt;
&lt;p&gt;You may notice most of the characteristics of procrastinators have
to do with their mindset. They keep delaying work because of some sort
of fear. This is exactly why tweaking our attitude towards work can
help us stop procrastinating and become more productive.&lt;/p&gt;
&lt;p&gt;Changing our mindset may seem a lot of work. But by doing the
smallest things every day, you&#039;re getting used to the way you handle
works - from setting goals, to breaking down tasks, to evaluating
each task&#039;s values.&lt;/p&gt;
&lt;p&gt;Source &lt;a href=&quot;http://www.lifehack.org/565818/why-procrastinate-and-how-stop-procrastination&quot;&gt;lifehack.org&lt;/a&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>manage busyness</title>
<link href="https://www.0ink.net/posts/2017/2017-03-24-manage-busyness.html"></link>
<id>urn:uuid:69cb2ceb-616f-9f22-e2dc-831f5d016a54</id>
<updated>2022-11-02T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Former United States President Dwight Eisenhower was responsible for
putting together one of the most important yet fundamentally simple
to understand concepts in time management. Eisenhower's
Urgent/Important Principle is a tool to help decipher what tasks need
to be addressed more immediately than others. Anyone who uses the
principle will be better able to organize and orchestrate their
...]]></summary>
<content type="html">&lt;p&gt;Former United States President Dwight Eisenhower was responsible for
putting together one of the most important yet fundamentally simple
to understand concepts in time management. Eisenhower&#039;s
Urgent/Important Principle is a tool to help decipher what tasks need
to be addressed more immediately than others. Anyone who uses the
principle will be better able to organize and orchestrate their
daily tasks. This skill is especially imperative for busy people
who find themselves working too hard and still not getting everything
done.&lt;/p&gt;
&lt;p&gt;Eisenhower&#039;s Urgent/Important Principle places tasks into four
categories:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Important and Urgent&lt;/li&gt;
&lt;li&gt;Important but Not Urgent&lt;/li&gt;
&lt;li&gt;Not Important but Urgent&lt;/li&gt;
&lt;li&gt;Not Important and Not Urgent&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These four categories are used to label and organize which tasks need
to be addressed first and which ones can be approached last. By
asserting something&#039;s importance and its urgency, we are better able
to identify what comes first:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2017/covey-time-management-grid.png&quot; alt=&quot;quadrants&quot; /&gt;&lt;/p&gt;
&lt;p&gt;What these quadrants reveal is that identifying which tasks are either
important or urgent boils down to time management and what makes us
most efficient. For example, President Obama&#039; former campaign manager
said in an article by WebMD that Obama valued his time to exercise and
that it helped fuel him for the rest of his day. According to Obama,
&amp;quot;The rest of my time will be more productive if you give me my workout
time.&amp;quot; The article goes on in detail about his routine and how he
values its importance.&lt;/p&gt;
&lt;p&gt;James Clear, a behavioral psychology writer, noted in a blog post
that &amp;quot;too often, we use productivity, time management, and optimization
as an excuse to avoid the really difficult question: &#039;Do I actually
need to be doing this?&#039; It is much easier to remain busy and tell
yourself that you just need to be a little more efficient or to &#039;work
a little later tonight&#039; than to endure the pain of eliminating a task
that you are comfortable with doing, but that isn&#039; the highest and
best use of your time.&amp;quot;&lt;/p&gt;
&lt;p&gt;Let&#039; take a deeper look at each quadrant, what it means, and how we
should approach all of our tasks with either urgency or importance
(or both).&lt;/p&gt;
&lt;h3 id=&quot;Urgent+And+Important&quot; name=&quot;Urgent+And+Important&quot;&gt;Urgent And Important&lt;/h3&gt;
&lt;p&gt;For Urgent/Important tasks, they can arise unexpectedly or may have
been left for the last minute. These tasks need to be managed ahead
of time. Make plans to address these tasks so that they do not become
stressful activities when it comes close to deadlines. It&#039;s also a
good idea to leave some wiggle room in your daily schedule just in
case unexpected tasks come about.&lt;/p&gt;
&lt;p&gt;Assess your deadlines. Are you moving at an appropriate pace to meet
that deadline?&lt;/p&gt;
&lt;p&gt;Emergencies happen. Whether they are unexpected meetings or sickness
or injuries, they can&#039;t be put off until later.&lt;/p&gt;
&lt;p&gt;This will force you to reconsider your task list and how much time
you have to apply to each quadrant.&lt;/p&gt;
&lt;h3 id=&quot;Important+But+Not+Urgent&quot; name=&quot;Important+But+Not+Urgent&quot;&gt;Important But Not Urgent&lt;/h3&gt;
&lt;p&gt;Not Urgent/Important tasks are integral to personal growth, building
relationships, and accomplishing long-term professional goals. If these
tasks are given the proper amount of time, they will not become urgent.
This will prevent unexpected and last-minute tasks from unexpectedly
cluttering up your time later on, keeping stress and frustration at
bay. You&#039;ll be able to complete work efficiently and effectively.&lt;/p&gt;
&lt;p&gt;Exercise is an example of this. Personal growth through exercise is
not an overnight progress. Training for a run or any other sort of
competition doesn&#039;t begin just days before. Plan your goals ahead of
time, but leave room for urgent, unexpected tasks.&lt;/p&gt;
&lt;p&gt;Maintaining your relationships is also important. Keep up with
friends and family and partners, but be mindful of how much time
you&#039;re alotting here. There is such a thing as putting too much
time into relationships. Your goals are important, too. If you keep
putting them off, they&#039;ll soon become urgent and you&#039;ll become
stressed. This may affect your relationships in the long run.&lt;/p&gt;
&lt;h3 id=&quot;Urgent+But+Not+Important&quot; name=&quot;Urgent+But+Not+Important&quot;&gt;Urgent But Not Important&lt;/h3&gt;
&lt;p&gt;Urgent/Not Important tasks are cumbersome and get in the way of
your goals. Responding to phone calls or emails that are not
pertinent to your goals or attending meetings with people who don&#039;t
bring any value to completing your activities can be wasted time.
Avoid these if possible and delegate the activities if you can.
Something to keep in mind: you&#039;re saying yes to the person, but no to
the task.&lt;/p&gt;
&lt;p&gt;If someone or something requires that you do things for them
frequently, then it might be best to arrange time for them in one
larger block of time. This will allow you to focus your energy and
time on multiple things.&lt;/p&gt;
&lt;p&gt;Respond to time-sensitive correspondence as needed. Don&#039;t wait until
after a deadline to inform someone when that deadline is:&lt;/p&gt;
&lt;p&gt;You: &amp;quot;Hey, the class will be starting at noon today.&amp;quot;&lt;/p&gt;
&lt;p&gt;Colleague: &amp;quot;Really? Because it&#039; already 2 P.M.!&amp;quot;&lt;/p&gt;
&lt;h3 id=&quot;Not+Urgent+And+Not+Important&quot; name=&quot;Not+Urgent+And+Not+Important&quot;&gt;Not Urgent And Not Important&lt;/h3&gt;
&lt;p&gt;Not Urgent/Not Important tasks should also be avoided. Spending
time on Facebook or Twitter, watching TV, and shopping (when it&#039;s
not important to completing your tasks to have the things you&#039;re
shopping for) can significantly drain your time. Limit these tasks
as much as possible. It&#039;s not always going to be easy saying no to
these mostly leisure activities, but it is important to remain
mindful of how much of that time you&#039;re using here.&lt;/p&gt;
&lt;p&gt;Yes, everyone is talking about the new show on Netflix. They watched
it this past weekend and are already posting memes and gifs on
Facebook. This doesn&#039;t mean you have to do the same.&lt;/p&gt;
&lt;p&gt;Complete tasks first and then assess if you have time to participate
in leisure. Otherwise, you&#039;re procrastinating, and that affects all
the other quadrants.&lt;/p&gt;
&lt;h3 id=&quot;In+Conclusion&quot; name=&quot;In+Conclusion&quot;&gt;In Conclusion&lt;/h3&gt;
&lt;p&gt;Eisenhower&#039;s Principles can be vital in developing skills to
effectively and consistently complete tasks, delegate properly,
and work efficiently. Take time to look over your tasks to determine
which quadrant they belong.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Is there a deadline? If yes, then it is important.&lt;/li&gt;
&lt;li&gt;Is the deadline soon? If yes, then it is urgent.&lt;/li&gt;
&lt;li&gt;Is the task necessary to completing the other tasks? If yes, then it is important.&lt;/li&gt;
&lt;li&gt;Can I delegate the task to someone else? If yes, then it is not important.&lt;/li&gt;
&lt;li&gt;What does it have to do with your personal growth?&lt;/li&gt;
&lt;li&gt;What does it have to do with your professional growth?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ask yourself these questions when you need to determine a task&#039;s
importance and urgency. Make a quadrant table of your own somewhere
to help you visualize all your tasks. This is an excellent exercise
for time management, and it could be the foundation of healthy work
habits that stick around for a long time.&lt;/p&gt;
&lt;p&gt;Source &lt;a href=&quot;http://www.lifehack.org/463821/if-youre-busy-but-still-find-your-hard-work-doesnt-pay-off-you-probably-lack-this-important-skill&quot;&gt;lifehack.org&lt;/a&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>Kivy</title>
<link href="https://www.0ink.net/posts/2017/2017-03-19-kivy.html"></link>
<id>urn:uuid:4282503e-c895-4b78-3ee6-74cfce1ee694</id>
<updated>2022-03-03T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Today I want to briefly write about kivy.  kivy is an
Python library intended for developing Mobile Apps.  It is a
cross-platform library that runs on Android, iOS,
Linux, OS X and Windows.  It is licensed under
the MIT, so it is free and open source.
Kivy is the main framework developed by the Kivy organisation, alongside with Python for Android,
...]]></summary>
<content type="html">&lt;p&gt;Today I want to briefly write about &lt;a href=&quot;https://kivy.org/&quot;&gt;kivy&lt;/a&gt;.  &lt;a href=&quot;https://kivy.org/&quot;&gt;kivy&lt;/a&gt; is an
&lt;a href=&quot;https://www.python.org/&quot;&gt;Python&lt;/a&gt; library intended for developing Mobile Apps.  It is a
cross-platform library that runs on &lt;a href=&quot;https://www.android.com/&quot;&gt;Android&lt;/a&gt;, &lt;a href=&quot;http://www.apple.com/ios/&quot;&gt;iOS&lt;/a&gt;,
&lt;a href=&quot;https://www.cs.helsinki.fi/linux/&quot;&gt;Linux&lt;/a&gt;, &lt;a href=&quot;https://www.apple.com/macos/&quot;&gt;OS X&lt;/a&gt; and &lt;a href=&quot;https://www.microsoft.com/en-us/windows&quot;&gt;Windows&lt;/a&gt;.  It is licensed under
the &lt;a href=&quot;https://opensource.org/licenses/MIT&quot;&gt;MIT&lt;/a&gt;, so it is free and open source.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://kivy.org/&quot;&gt;Kivy&lt;/a&gt; is the main framework developed by the &lt;a href=&quot;https://kivy.org/#aboutus&quot;&gt;Kivy organisation&lt;/a&gt;, alongside with &lt;a href=&quot;https://github.com/kivy/python-for-android&quot;&gt;Python for Android&lt;/a&gt;,
&lt;a href=&quot;https://github.com/kivy/kivy-ios&quot;&gt;Kivy iOS&lt;/a&gt; and other libraries meant to be used in all platforms.  It is compatible with Python2 and Python3,
as well as having support for the Raspberry Pi.&lt;/p&gt;
&lt;p&gt;The framework contains all the elements for building an application such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;extensive input support for mouse, keyboard, TUIO, and OS-specific multitouch events,&lt;/li&gt;
&lt;li&gt;a graphic library using only OpenGL ES 2, and based on Vertex Buffer Object and shaders,&lt;/li&gt;
&lt;li&gt;a wide range of Widgets that support multitouch,&lt;/li&gt;
&lt;li&gt;an intermediate language &lt;a href=&quot;https://kivy.org/docs/guide/lang.html&quot;&gt;kv&lt;/a&gt; used to easily design custom Widgets.&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Mail Archiver ideas</title>
<link href="https://www.0ink.net/posts/2017/2017-03-10-mail-archiver-ideas.html"></link>
<id>urn:uuid:53e64392-3a5f-e6db-e006-bef6b1b112fc</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[We use it for receiving junk e-mails (i.e. for those times where we need an e-mail address for sign-up to a service).
E-mails are of the form:
ar-XXXX@0ink.net
TODO:
Extend postie:

...]]></summary>
<content type="html">&lt;p&gt;We use it for receiving junk e-mails (i.e. for those times where we need an e-mail address for sign-up to a service).&lt;/p&gt;
&lt;p&gt;E-mails are of the form:&lt;/p&gt;
&lt;p&gt;ar-XXXX@0ink.net&lt;/p&gt;
&lt;h3 id=&quot;TODO%3A&quot; name=&quot;TODO%3A&quot;&gt;TODO:&lt;/h3&gt;
&lt;p&gt;Extend postie:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://postieplugin.com/extending/&quot;&gt;http://postieplugin.com/extending/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://postieplugin.com/postie_post_before/&quot;&gt;http://postieplugin.com/postie_post_before/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Before posting we insert all the header information into a table.&lt;/p&gt;
&lt;p&gt;Automatically delete postings.  If we want to keep the post we change its category.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://wordpress.org/plugins/auto-prune-posts/&quot;&gt;https://wordpress.org/plugins/auto-prune-posts/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;MAYBE: Markdownify it...&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/Elephant418/Markdownify&quot;&gt;https://github.com/Elephant418/Markdownify&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;EXTRA ARCHIVER:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Check how gmail keeps folders (&lt;a href=&quot;http://php.net/manual/en/function.imap-open.php&quot;&gt;http://php.net/manual/en/function.imap-open.php&lt;/a&gt;)
And then see if we can hack it into Postie.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.electrictoolbox.com/open-mailbox-other-than-inbox-php-imap/&quot;&gt;https://www.electrictoolbox.com/open-mailbox-other-than-inbox-php-imap/&lt;/a&gt;
Maybe we do by e-mail@something/folder&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;http://postieplugin.com/forcing-an-email-check/&quot;&gt;http://postieplugin.com/forcing-an-email-check/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Would be the call back to MailGun&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Check if we can add MailGun to Postie
&lt;ul&gt;
&lt;li&gt;save transient when we start&lt;/li&gt;
&lt;li&gt;Use event API to get message lists (since last transient)&lt;/li&gt;
&lt;li&gt;Use message API to retrieve and delete messages&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;E-mail archiving alternatives
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://lurker.sourceforge.net/&quot;&gt;lurker &lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.enkive.org/&quot;&gt;Enkive&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://terminal.se/code.html&quot;&gt;mboxpurge.pl&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://archivemail.sourceforge.net/&quot;&gt;archivemail&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sourceforge.net/projects/openmailarchiva/&quot;&gt;Open Mail Archiva&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://git.io/gyb&quot;&gt;GYB&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://gmvault.org&quot;&gt;gmvault&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.mailpiler.org/wiki/start&quot;&gt;Mail Piler&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Fixed drive letters for removable USB sticks</title>
<link href="https://www.0ink.net/posts/2017/2017-02-03-fixed-drive-letters-for-removable-usb-sticks.html"></link>
<id>urn:uuid:54bffae1-fe8d-a569-06b9-1780cc5de3e1</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[If you use multiple USB drives, you've probably noticed that the drive letter can be
different each time you plug one in. If you'd like to assign a static letter to a drive that's
the same every time you plug it in, read on.
Windows assigns drive letters to whatever type of drive is available. This can be annoying
especially if you use backup tools or portable apps that prefer to have the same drive letter
every time.
...]]></summary>
<content type="html">&lt;p&gt;If you use multiple USB drives, you&#039;ve probably noticed that the drive letter can be
different each time you plug one in. If you&#039;d like to assign a static letter to a drive that&#039;s
the same every time you plug it in, read on.&lt;/p&gt;
&lt;p&gt;Windows assigns drive letters to whatever type of drive is available. This can be annoying
especially if you use backup tools or portable apps that prefer to have the same drive letter
every time.&lt;/p&gt;
&lt;p&gt;To work with drive letters, you&#039;ll use the Disk Management tool built into Windows. In Windows
7, 8, or 10, click Start, type&lt;code&gt;create and format&lt;/code&gt;, and then click &lt;code&gt;Create and format hard disk partitions.&lt;/code&gt; Don&#039;t worry. You&#039;re not going to be formatting or creating anything. That&#039;s just
the Start menu entry for the Disk Management tool. This procedure works the same in pretty much
any version of Windows (though in Windows XP and Vista, you&#039;d need to launch Disk Management
through the Administrative Tools item in the Control Panel).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2017/sud_1.png&quot; alt=&quot;sud_1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Windows will scan and then display all the drives connected to your PC in the Disk Management
window. Right-click the USB drive to which you want to assign a persistent drive letter and
then click &lt;code&gt;Change Drive Letter and Paths.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2017/sud_2.png&quot; alt=&quot;sud_2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;Change Drive Letter and Paths&lt;/code&gt; window the selected drive&#039;s current drive letter. To
change the drive letter, click &lt;code&gt;Change.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2017/sud_3.png&quot; alt=&quot;sud_3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In the &lt;code&gt;Change Drive Letter or Path&lt;/code&gt; window that opens, make sure the &lt;code&gt;Assign the following drive letter&lt;/code&gt; option is selected and then use the drop-down menu to select a new drive letter.
When you&#039;re done, click &lt;code&gt;OK.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;NOTE: We suggest picking a drive letter between M and Z, because earlier drive letters may
still get assigned to drives that don&#039;t always show up in File Explorer-like optical and
removable card drives. M through Z are almost never used on most Windows systems.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2017/sud_4.png&quot; alt=&quot;sud_4&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Windows will display a warning letting you know that some apps might rely on drive letters
to run properly. For the most part, you won&#039;t have to worry about this. But if you do have
any apps in which you&#039;ve specified another drive letter for this drive, you may need to
change them. Click &lt;code&gt;Yes&lt;/code&gt; to continue.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2017/sud_5.png&quot; alt=&quot;sud_5&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Back in the main Disk Management window, you should see the new drive letter assigned to the
drive. You can now close the Disk Management window.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2017/sud_6.png&quot; alt=&quot;sud_6&quot; /&gt;&lt;/p&gt;
&lt;p&gt;From now on, when you disconnect and reconnect the drive, that new drive letter should persist.
You can also now use fixed paths for that drive in apps &lt;code&gt;such as back up apps&lt;/code&gt; that may require them.&lt;/p&gt;
&lt;p&gt;Source: &lt;a href=&quot;http://www.howtogeek.com/96298/assign-a-static-drive-letter-to-a-usb-drive-in-windows-7/&quot;&gt;howtogeek&lt;/a&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>Portable Console</title>
<link href="https://www.0ink.net/posts/2017/2017-01-25-portable-console.html"></link>
<id>urn:uuid:edc29a39-9e42-83ab-7162-c81b0e88f066</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[portable console
Set scrolling region:
printf "\033[1;24r"
Reset scrolling region:
printf "\033[r"
However, it is easier/better to do:
...]]></summary>
<content type="html">&lt;p&gt;portable console&lt;/p&gt;
&lt;p&gt;Set scrolling region:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;printf &quot;\033[1;24r&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reset scrolling region:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;printf &quot;\033[r&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, it is easier/better to do:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;stty rows 24 cols 80&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>Xnest</title>
<link href="https://www.0ink.net/posts/2017/2017-01-24-xnest.html"></link>
<id>urn:uuid:6e6a6d70-070f-8dfc-54f5-9f2094170ea8</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This trick lets you run X-Windows within an X-Windows session.
This is kinda like running VNC.  It is useful for testing scenarios.
#!/bin/sh
Xnest :1 -name "Bla" -ac -geometry 800x600 &amp;amp;
sleep 1
export DISPLAY=:1
...]]></summary>
<content type="html">&lt;p&gt;This trick lets you run X-Windows within an X-Windows session.&lt;/p&gt;
&lt;p&gt;This is kinda like running VNC.  It is useful for testing scenarios.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/sh
Xnest :1 -name &quot;Bla&quot; -ac -geometry 800x600 &amp;amp;amp;
sleep 1
export DISPLAY=:1
exec xterm&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>CyberWorld 2017.1</title>
<link href="https://www.0ink.net/posts/2017/2017-01-20-cyberworld-2017.html"></link>
<id>urn:uuid:34b6c15c-90d3-d2f4-3855-840bc1355b63</id>
<updated>2022-01-04T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Development


travis cordova build


...]]></summary>
<content type="html">&lt;p&gt;Development&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/qertis/acd71e14db4168832f3b67c75182af04/&quot;&gt;travis cordova build&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/svenlaater/travis-ci-ionic-yml&quot;&gt;travis ionic build&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;owx&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;common
&lt;ul&gt;
&lt;li&gt;muninlite (can it support plugins?)&lt;/li&gt;
&lt;li&gt;flock, pwgen, ifstat&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;ow1
&lt;ul&gt;
&lt;li&gt;diags&amp;amp;tools: usbutils, netstat-nat&lt;/li&gt;
&lt;li&gt;sniffer: tcpdump[-mini] 317K/617K, libpcap 191K&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;owX
&lt;ul&gt;
&lt;li&gt;FW/NAT&lt;/li&gt;
&lt;li&gt;DNSMASQ: DHCP + DNS&lt;/li&gt;
&lt;li&gt;NTP server&lt;/li&gt;
&lt;li&gt;Dynamic DNS updating (mushu porker)&lt;/li&gt;
&lt;li&gt;NFS&lt;/li&gt;
&lt;li&gt;IPv6 tunnel&lt;/li&gt;
&lt;li&gt;Provisioning server: (PXE, TFTP, NFS, HTTP, rpmgot, syslog?)&lt;/li&gt;
&lt;li&gt;TLR server: HTTP, file manipulation, HTTPS?&lt;/li&gt;
&lt;li&gt;USB storage&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;owx - switches&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;cn1&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; disabled &gt; data scrubbing&lt;/li&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; disabled &gt; backups&lt;/li&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; disabled &gt; boot cd mirroring&lt;/li&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; disabled &gt; config backup to alvm1&lt;/li&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; disabled &gt; NFS mounting installable iso images&lt;/li&gt;
&lt;li&gt;alvm1 : Main file store
&lt;ul&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; disabled checked&gt; file sharing (NFS, Samba, http)&lt;/li&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; disabled &gt; rsync backup target&lt;/li&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; disabled &gt; undup, backup puller&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; disabled &gt; alvm2 : Backup file store
&lt;ul&gt;
&lt;li&gt;snapshot server (NFS)&lt;/li&gt;
&lt;li&gt;backuper&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; disabled &gt; alvm3 : Transmission
&lt;ul&gt;
&lt;li&gt;Implemented as its own server because of the VPN&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; disabled &gt; cvm1 : Main APP server&lt;/li&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; disabled &gt; alvm4 : X10 server
&lt;ul&gt;
&lt;li&gt;Implemented as its own server because VM only runs if HW is available&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; disabled &gt; alvm5: DMZ Server
&lt;ul&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; disabled &gt; reverse proxy&lt;/li&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; disabled &gt; PocketMine
&lt;ul&gt;
&lt;li&gt;Muirfield&lt;/li&gt;
&lt;li&gt;Niños&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; disabled &gt; asterisk&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;input type=&quot;checkbox&quot; disabled &gt; alvm6 : Scan&amp;amp;Print server
&lt;ul&gt;
&lt;li&gt;Spin-off cvm1, because SELINUX exception.  Shouldn&#039;t connect to DMZ, nor X10&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;DMZ+Server+Basic+Alpine+Linux+install&quot; name=&quot;DMZ+Server+Basic+Alpine+Linux+install&quot;&gt;DMZ Server Basic Alpine Linux install&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Create dos partition on the data drive&lt;/li&gt;
&lt;li&gt;mkdosfs on partition and mount&lt;/li&gt;
&lt;li&gt;setup-alpine&lt;/li&gt;
&lt;li&gt;apk update&lt;/li&gt;
&lt;li&gt;lbu ci&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3 id=&quot;Reverse+Proxy&quot; name=&quot;Reverse+Proxy&quot;&gt;Reverse Proxy&lt;/h3&gt;
&lt;h4 id=&quot;install+nginx&quot; name=&quot;install+nginx&quot;&gt;install nginx&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;apk add nginx ?php-fpm?&lt;/li&gt;
&lt;li&gt;configure in /etc/nginx/nginx.conf (&lt;a href=&quot;https://wiki.alpinelinux.org/wiki/OwnCloud#Nginx&quot;&gt;Reference&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;apk apache2-utils : for htpasswd command&lt;/li&gt;
&lt;li&gt;Add a proxy command:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;    location / {
      proxy_pass http://$server/;
      auth_basic &quot;Restricted&quot;;
      auth_basic_user_file /etc/nginx/_htpasswd;
      proxy_set_header X-Remote-User $remote_user;
      proxy_pass_request_headers      on;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Variable Reference: &lt;a href=&quot;http://nginx.org/en/docs/http/ngx_http_core_module.html#variables&quot;&gt;NGINX Docs&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3 id=&quot;WebServer&quot; name=&quot;WebServer&quot;&gt;WebServer&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;PHP checks headers (passed by the reverse proxy), otherwise,&lt;/li&gt;
&lt;li&gt;use authd:&lt;/li&gt;
&lt;li&gt;If using selinux we need to set this boolean:
&lt;ul&gt;
&lt;li&gt;setsebool -P httpd_can_network_connect on&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;PHP Function on server to determine user:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;define(&#039;IDENT_PORT&#039;,113);

function identd_query($remote_ip,$remote_port,$local_port,$tout=3) {
  $remote_ip = &#039;localhost&#039;;
  $sock = @fsockopen($remote_ip,IDENT_PORT,$errno,$errstr,$tout);
  //print_r([$sock,$errno,$errstr]);
  if (!$sock) return FALSE;
  @fwrite($sock,$remote_port.&#039;,&#039;.$local_port.&quot;\r\n&quot;);
  $line = @fgets($sock,1000); // 1000 octets according to RFC1413
  fclose($socket);
  if (preg_match(&#039;/^\s*(\d+)\s*,\s*(\d+)\s*:\s*(\S+)\s*:\s*(\S+)\s*:\s*(\S+)\s*$/&#039;, $line,$mv)) {
    if ($mv[1] == $remote_port &amp;amp;&amp;amp; $mv[2] == $local_port &amp;amp;&amp;amp;
    $mv[3] == &#039;USERID&#039;) {
      return $mv[5];
    }
  }
  return FALSE;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Web+Browser&quot; name=&quot;Web+Browser&quot;&gt;Web Browser&lt;/h3&gt;
&lt;p&gt;For archlinux, install oidentd&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;yum install authd&lt;/li&gt;
&lt;li&gt;check firewall port&lt;/li&gt;
&lt;li&gt;systemctl start authd.socket&lt;/li&gt;
&lt;li&gt;Enable authd.socket&lt;/li&gt;
&lt;li&gt;Add Override:
&lt;ul&gt;
&lt;li&gt;/etc/systemd/system/auth@.service.d/override.conf
&lt;ul&gt;
&lt;li&gt;[Service]&lt;/li&gt;
&lt;li&gt;ExecStart=&lt;/li&gt;
&lt;li&gt;ExecStart=-/usr/sbin/in.authd -t60 --xerror&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.alpinelinux.org/wiki/LXC&quot;&gt;https://wiki.alpinelinux.org/wiki/LXC&lt;/a&gt;
&lt;a href=&quot;https://wiki.alpinelinux.org/wiki/Setting_up_a_basic_vserver&quot;&gt;https://wiki.alpinelinux.org/wiki/Setting_up_a_basic_vserver&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;browser -&amp;gt; guac -&amp;gt; xinetd|vncserver|x2go-client -&amp;gt; x2go-server
browser -&amp;gt; revproxy -&amp;gt; guac -&amp;gt; xinetd|vncserver|x2go-client -&amp;gt; x2go-server&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Check what Thin client software Tiny Core Linux supports otherwise Browser with Guacamole&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Server script (haserl) on OW1
Show version and last update
Options: Delete Entry
Post update : Using wget&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Create a local pastebin (to add notes from SONY PRS-T2)
&lt;a href=&quot;https://wiki.alpinelinux.org/wiki/Pastebin&quot;&gt;https://wiki.alpinelinux.org/wiki/Pastebin&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3 id=&quot;Configure+a+Windows+VM&quot; name=&quot;Configure+a+Windows+VM&quot;&gt;Configure a Windows VM&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;./mxt.sh \
vmcfg \
vm=winvm1 \
rem=&quot;win7 system&quot; \
-serial \
viridian=1 \
boot=d \
hd=1,16G \
cdrom=3,/xendat/installers/Win7AIO.x32-x64.preact.iso&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Centos+7&quot; name=&quot;Centos+7&quot;&gt;Centos 7&lt;/h3&gt;
&lt;p&gt;Template preparation &lt;/p&gt;
&lt;p&gt;Configure serial console:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Modify &lt;code&gt;/etc/default/grub&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;GRUB_TERMINAL_OUTPUT=serial&lt;/li&gt;
&lt;li&gt;GRUB_CMDLINE_LINUX=console=ttyS0 --rhgb&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;grub2-mkconfig -o $d/grub.cfg&lt;/code&gt; either on &lt;code&gt;/boot/efi/EFI&lt;/code&gt; or &lt;code&gt;/boot/grub2&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Stop ssh and remove all ssh keys. &lt;/p&gt;
&lt;p&gt;Modify rc.local to run something once:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;change hostname (if possible?)&lt;/li&gt;
&lt;li&gt;remove all SSH keys (and reboot)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Create a centos/xen template prep script. We pass it as a custom tar in xvdh.  Another option:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use a serial port (connected to UNIX socket)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://xenbits.xen.org/docs/unstable/misc/channel.txt&quot;&gt;Use a Xen PV channel&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We need to pass vm name. &lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.certdepot.net/rhel7-get-started-systemd/&quot;&gt;rhel7 and systemd&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Cfg script /etc/xen&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;After block devices stanza&lt;/li&gt;
&lt;li&gt;Check if tar is there&lt;/li&gt;
&lt;li&gt;Append to the list&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Better to do &lt;a href=&quot;http://silviud.blogspot.nl/2011/09/from-domu-read-xenstore-ec2-linode-etc.html?m=1&quot;&gt;this&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;Notes&quot; name=&quot;Notes&quot;&gt;Notes&lt;/h3&gt;
&lt;h4 id=&quot;munin&quot; name=&quot;munin&quot;&gt;munin&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://munin-monitoring.org/wiki/HowToWritePlugins&quot;&gt;plugin writing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Monitored data using &lt;a href=&quot;http://support.citrix.com/article/CTX127896&quot;&gt;xentop&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;CPU is done, what about vbd I/O or network I/O&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;xen wiki on &lt;a href=&quot;http://wiki.xenproject.org/wiki?title=Special%3ASearch&amp;amp;search=xentop&amp;amp;go=Go&quot;&gt;xentop&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Serial+xen+configuration&quot; name=&quot;Serial+xen+configuration&quot;&gt;Serial xen configuration&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;serial=/dev/ttyS0&lt;br /&gt;
[Linux only] Use host tty, e.g. ‘/dev/ttyS0’. The host serial port parameters are set according to
the emulated ones.&lt;/li&gt;
&lt;li&gt;serial=unix:path[,server][,nowait]&lt;br /&gt;
A unix domain socket is used instead of a tcp socket. The option works the
same as if you had specified -serial tcp except the unix domain socket path
is used for connections.&lt;br /&gt;
The TCP Net Console has two modes of operation. It can send the serial
I/O to a location or wait for a connection from a location. By default the
TCP Net Console is sent to host at the port. If you use the server option
QEMU will wait for a client socket application to connect to the port before
continuing, unless the nowait option was specified. The nodelay option
disables the Nagle buffering algorithm. If host is omitted, 0.0.0.0 is assumed.
Only one TCP connection at a time is accepted. You can use telnet to connect
to the corresponding character device.&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Side Load apps on Android TV</title>
<link href="https://www.0ink.net/posts/2017/2017-01-10-side-load-apps-on-android-tv.html"></link>
<id>urn:uuid:47b85517-b980-df40-e6a3-53c9de25a091</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[So we bough a Philips 50PFK6540.  This is a 50&quot; TV with
Ambi Ligh and
Android TV.
One of the things I wanted to do from the very start was to load my own APKs.  This was not
possible until a recent (2016) update that enabled the &quot;Install from Unknown Sources&quot;
setting option.
...]]></summary>
<content type="html">&lt;p&gt;So we bough a Philips 50PFK6540.  This is a 50&amp;quot; TV with
&lt;a href=&quot;https://en.wikipedia.org/wiki/Ambilight&quot;&gt;Ambi Ligh&lt;/a&gt; and
&lt;a href=&quot;https://en.wikipedia.org/wiki/Android_TV&quot;&gt;Android TV&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;One of the things I wanted to do from the very start was to load my own APKs.  This was not
possible until a recent (2016) update that enabled the &lt;strong&gt;&amp;quot;Install from Unknown Sources&amp;quot;&lt;/strong&gt;
setting option.&lt;/p&gt;
&lt;p&gt;This made it possible to side load applications.  However, things are not as easy as I
initially thought.  Because while installing from unknown sources was possible, you can not
do this from the built-in browser.  So the procedure is as folows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Go to settings to enable &lt;strong&gt;Install from Unknown sources&lt;/strong&gt;, which should be under
&lt;code&gt;Security &amp;amp;amp; Restrictions&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Download &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.estrongs.android.pop&quot;&gt;ES File Explorer&lt;/a&gt;
from the Play Store.  A bit of a warning: on phones and tablets, ES File Explorer isn&#039;t something
that is generally recommended as it used to be a reliable file manager that was one of the
most valuable Android apps, but recently it became riddled with ads &lt;em&gt;many of which are highly
intrusive) leading many users to uninstall it and websites to remove it from their &lt;/em&gt;must have* lists.
Fortunately, the Android TV app seems to have gone largely untouched by this, so it still is
recommended it for the purpose of this tutorial.&lt;/li&gt;
&lt;li&gt;Use ES File Explorer to download the APK you want to side load.  There is a number of ways to do
this.  I used the built-in FTP server.  But you could use any method (i.e Thumb drive, Cloud
Storage, etc...)&lt;/li&gt;
&lt;li&gt;Open the APK from ES File Explorer and install it.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;/images/2017/50PFK6540_12-IMS-nl_NL.png&quot; alt=&quot;Philips 50PFK6540&quot; /&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>Building Signed APKs</title>
<link href="https://www.0ink.net/posts/2016/2016-12-11-building-signed-apks.html"></link>
<id>urn:uuid:8dc4671d-6d30-fef8-4957-051bbb572982</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Building signed APK's for Android is easy if you know what you
are doing.
This article goes over the preparation steps and the additional
build instructions needed to created signed APKs.
Preparation
First you need to have a keystore.  Use this command:
...]]></summary>
<content type="html">&lt;p&gt;Building signed APK&#039;s for Android is easy if you know what you
are doing.&lt;/p&gt;
&lt;p&gt;This article goes over the preparation steps and the additional
build instructions needed to created signed APKs.&lt;/p&gt;
&lt;h3 id=&quot;Preparation&quot; name=&quot;Preparation&quot;&gt;Preparation&lt;/h3&gt;
&lt;p&gt;First you need to have a &lt;code&gt;keystore&lt;/code&gt;.  Use this command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
keystore_file=&quot;my_key_store.keystore&quot;
key_name=&quot;john_doe&quot;
secret=&#039;fake_password&#039;
name=&#039;John Doe&#039;
dept=&#039;Engineering&#039;
org=&#039;TLabs Inc&#039;
place=&#039;New York&#039;
province=&#039;NY&#039;
country=&#039;US&#039;

keytool -genkey -v -keystore &quot;$keystore_file&quot; -alias &quot;key_name&quot; -keyalg &quot;RSA&quot; -validity 10000 -storepass &quot;$secret&quot; -keypass &quot;$secret&quot; &amp;amp;lt;&amp;amp;lt;EOF
$name
$dept
$org
$place
$province
$country
yes
EOF
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Remember the keystore file and passwords.&lt;/p&gt;
&lt;h3 id=&quot;Build+instructions&quot; name=&quot;Build+instructions&quot;&gt;Build instructions&lt;/h3&gt;
&lt;p&gt;In your &lt;code&gt;build.gradle&lt;/code&gt; you need the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;
android {
  signingConfigs {
    release {
      storeFile file(&quot;my_keystore.keystore&quot;)
      storePassword &quot;{password}&quot;
      keyAlias &quot;Key_Alias&quot;
      keyPassword &quot;{password}&quot;
    }
  }
  buildTypes {
    release {
      signingConfig signingConfigs.release
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>Archiving DVDs and CDs</title>
<link href="https://www.0ink.net/posts/2016/2016-12-05-archiving-dvds-and-cds.html"></link>
<id>urn:uuid:b41ab038-e803-614b-0f57-7492b22fb421</id>
<updated>2021-12-26T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Since now I have a Android TV I put away my HTPC and with that the capability to view DVDs or
listen CDs directly.
So I converted my entire CD and DVD library to media files and stored in my home NAS.
Since we are talking hundreds of DVDs and CDs, I was using some tools.
CD ripping
For CD ripping, pretty much everything can be done with abcde.
...]]></summary>
<content type="html">&lt;p&gt;Since now I have a Android TV I put away my HTPC and with that the capability to view DVDs or
listen CDs directly.&lt;/p&gt;
&lt;p&gt;So I converted my entire CD and DVD library to media files and stored in my home NAS.&lt;/p&gt;
&lt;p&gt;Since we are talking hundreds of DVDs and CDs, I was using some tools.&lt;/p&gt;
&lt;h2 id=&quot;CD+ripping&quot; name=&quot;CD+ripping&quot;&gt;CD ripping&lt;/h2&gt;
&lt;p&gt;For CD ripping, pretty much everything can be done with &lt;a href=&quot;https://abcde.einval.com/wiki/&quot;&gt;abcde&lt;/a&gt;.
I would use the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;abcde -G -k -o mp3 -x&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-G&lt;/code&gt; : Get album art.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-k&lt;/code&gt; : Keep &lt;code&gt;wav&lt;/code&gt; after encoding.  This is not really necessary.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-o mp3&lt;/code&gt; : Output to &lt;code&gt;mp3&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-x&lt;/code&gt; : Eject the CD after all tracks have been read.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Afterwards I would use &lt;a href=&quot;http://eyed3.nicfit.net/&quot;&gt;eyeD3&lt;/a&gt; to embed the
cover art and tweak things.  (Note under &lt;a href=&quot;http://archlinux.org&quot;&gt;archlinux&lt;/a&gt;,
&lt;code&gt;eyeD3&lt;/code&gt; is installed from the &lt;code&gt;python2-eyed3&lt;/code&gt; package).&lt;/p&gt;
&lt;h4 id=&quot;To+add+cover+art%3A&quot; name=&quot;To+add+cover+art%3A&quot;&gt;To add cover art:&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;eyeD3 --add-image=&quot;$cover_file&quot;:FRONT_COVER \*.mp3&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;DVD+Ripping&quot; name=&quot;DVD+Ripping&quot;&gt;DVD Ripping&lt;/h2&gt;
&lt;p&gt;For DVD Ripping I was using a couple of homegrown scripts.  These can be found on
&lt;a href=&quot;https://github.com/alejandroliu/MediaArchiving&quot;&gt;github&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I started using &lt;a href=&quot;http://vobcopy.org/download/release_notes_and_download.shtml&quot;&gt;vobcopy&lt;/a&gt;,
but if I were to do this again I would use &lt;a href=&quot;http://dvdbackup.sourceforge.net/&quot;&gt;dvdbackup&lt;/a&gt;
with the &lt;code&gt;-M&lt;/code&gt; option.  &lt;code&gt;vobcopy&lt;/code&gt; is quite old and probably is orphaned by now.&lt;/p&gt;
&lt;h3 id=&quot;Scripts+for+archiving+media&quot; name=&quot;Scripts+for+archiving+media&quot;&gt;Scripts for archiving media&lt;/h3&gt;
&lt;p&gt;Scripts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;archive-dvd : Create an iso image from a DVD.&lt;/li&gt;
&lt;li&gt;alltitles : Extract titles/chapters from a DVD.&lt;/li&gt;
&lt;li&gt;auto.sh : Used to transcode titles/chapters extracted by &lt;code&gt;alltitles&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;archive-dvd&quot; name=&quot;archive-dvd&quot;&gt;archive-dvd&lt;/h4&gt;
&lt;p&gt;This script uses &lt;code&gt;vobcopy&lt;/code&gt; and &lt;code&gt;mkisofs&lt;/code&gt; to create an ISO file.
Just run the script and insert a DVD, you will get an ISO file
in return.&lt;/p&gt;
&lt;h4 id=&quot;alltitles&quot; name=&quot;alltitles&quot;&gt;alltitles&lt;/h4&gt;
&lt;p&gt;Usage:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[option_vars] sh alltitles [chapter]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Option vars:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;drive=[device-path] defaults to /dev/sr0&lt;/li&gt;
&lt;li&gt;titles=&amp;quot;01 02 03 ...&amp;quot; defaults to all titles in DVD (as listed by
lsdvd)
You can also specify titles as:
&lt;code&gt;title=&quot;01,1-4 01,5-8&quot;&lt;/code&gt;
This will create two files, one with track 1, chaptes one trough
four (inclusive)
and another one with track 1, chapters five through eigth (inclusive)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Command options:&lt;/p&gt;
&lt;p&gt;chapter: Leave blank for all chapters, otherwise:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-chapter [$start-$end]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Will dump starting from $start until $end. (or end)
If you only want to extract chapter 7 by itself, use -chapter 7-7&lt;/p&gt;
&lt;h4 id=&quot;auto.sh&quot; name=&quot;auto.sh&quot;&gt;auto.sh&lt;/h4&gt;
&lt;p&gt;Usage:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sh $0 [options]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;vob files must be the ones extracted from &lt;code&gt;alltitles&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;--preview|-p : Only encode 30 seconds from 4 minutes in&lt;/li&gt;
&lt;li&gt;--copy|-c : Do only copy&lt;/li&gt;
&lt;li&gt;--interlace|-i : Force interlace filter&lt;/li&gt;
&lt;li&gt;--no-interlace|+i : Disables interlace filter&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Dependancies&quot; name=&quot;Dependancies&quot;&gt;Dependancies&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;libdvdcss (or equivalent).
This is used by the dvdread library to decode CSS protected DVDs.&lt;/li&gt;
&lt;li&gt;libdvdread
This is used to read DVD by a number of binaries.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://vobcopy.org/download/release_notes_and_download.shtml&quot;&gt;vobcopy&lt;/a&gt;
Used by &lt;code&gt;archive-dvd&lt;/code&gt; to extract the data that will be used to create
the ISO image.  Uses &lt;code&gt;libdvdread&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;udisks or udisks2
Used by the scripts to detect when a CD/DVD is inserted.&lt;/li&gt;
&lt;li&gt;cdrkit
Used to create the iso images by &lt;code&gt;archive-dvd&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;lsdvd
Used by &lt;code&gt;alltitles.sh&lt;/code&gt; to get track information.&lt;/li&gt;
&lt;li&gt;mplayer
Used by &lt;code&gt;alltitles.sh&lt;/code&gt; to extract DVD titles/chapters.&lt;/li&gt;
&lt;li&gt;ffmpeg
Used by &lt;code&gt;alltitles.sh&lt;/code&gt; to encode video.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Some+useful+commands&quot; name=&quot;Some+useful+commands&quot;&gt;Some useful commands&lt;/h3&gt;
&lt;p&gt;Using &lt;code&gt;mplayer&lt;/code&gt; to play extract:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mplayer -dvd-device /dev/sr0 dvd://$title -chapter $chapter-$chapter -dumpstream -dumpfile ~/$title.VOB&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>Writing Safe Shell scripts</title>
<link href="https://www.0ink.net/posts/2016/2016-11-23-writing-safe-shell-scripts.html"></link>
<id>urn:uuid:6ac46b96-de12-43e9-b696-c5a11ab76d7c</id>
<updated>2024-11-22T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[Writing shell scripts leaves a lot of room to make mistakes, in ways that will cause
your scripts to break on certain input, or (if some input is untrusted) open up security
vulnerabilities. Here are some tips on how to make your shell scripts safer.
Don't
The simplest step is to avoid using shell at all. Many higher-level languages are both
easier to write the code in in the first place, and avoid some of the issues that shell
...]]></summary>
<content type="html">&lt;p&gt;Writing shell scripts leaves a lot of room to make mistakes, in ways that will cause
your scripts to break on certain input, or (if some input is untrusted) open up security
vulnerabilities. Here are some tips on how to make your shell scripts safer.&lt;/p&gt;
&lt;h2 id=&quot;Don%27t&quot; name=&quot;Don%27t&quot;&gt;Don&#039;t&lt;/h2&gt;
&lt;p&gt;The simplest step is to avoid using shell at all. Many higher-level languages are both
easier to write the code in in the first place, and avoid some of the issues that shell
has. For example, Python will automatically error out if you try to read from an
uninitialized variable (though not if you try to write to one), or if some function call
you make produces an error.&lt;/p&gt;
&lt;p&gt;One of shell&#039;s chief advantages is that it&#039;s easy to call out to the huge variety of
command-line utilities available. Much of that functionality will be available through
libraries in Python or other languages. For the handful of things that aren&#039;t, you can
still call external programs. In Python, the
&lt;a href=&quot;https://docs.python.org/2/library/subprocess.html&quot;&gt;subprocess&lt;/a&gt;
module is very useful for this.  You should try to avoid passing &lt;code&gt;shell=True&lt;/code&gt; to &lt;code&gt;subprocess&lt;/code&gt;
(or using &lt;code&gt;os.system&lt;/code&gt; or similar functions at all), since that will run a shell, exposing
you to many of the same issues as plain shell has. It also has two big advantages over
shell: it&#039;s a lot easier to avoid
&lt;a href=&quot;http://www.gnu.org/software/bash/manual/html_node/Word-Splitting.html&quot;&gt;word-splitting&lt;/a&gt;
or similar issues, and since calls to &lt;code&gt;subprocess&lt;/code&gt; will tend to be relatively uncommon,
it&#039;s easy to scrutinize them especially hard. When using &lt;code&gt;subprocess&lt;/code&gt; or similar tools,
you should still be aware of the suggestions in &amp;quot;Passing filenames or other positional
arguments to commands&amp;quot; below.&lt;/p&gt;
&lt;h2 id=&quot;Shell+settings&quot; name=&quot;Shell+settings&quot;&gt;Shell settings&lt;/h2&gt;
&lt;p&gt;POSIX sh and especially bash have a number of settings that can help write safe shell
scripts.&lt;/p&gt;
&lt;p&gt;I recommend the following in bash scripts:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;set -euf -o pipefail&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In dash, &lt;code&gt;set -o&lt;/code&gt; doesn&#039;t exist, so use only &lt;code&gt;set -euf&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;What do those do?&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html&quot;&gt;set -e&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If a command fails, &lt;code&gt;set -e&lt;/code&gt; will make the whole script exit, instead of just resuming
on the next line. If you have commands that can fail without it being an issue, you can
append &lt;code&gt;|| true&lt;/code&gt; or &lt;code&gt;|| :&lt;/code&gt; to suppress this behavior - for example &lt;code&gt;set -e&lt;/code&gt; followed by
&lt;code&gt;false || :&lt;/code&gt; will not cause your script to terminate.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html&quot;&gt;set -u&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Treat unset variables as an error, and immediately exit.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html&quot;&gt;set -f&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Disable filename expansion (globbing) upon seeing &lt;code&gt;*&lt;/code&gt;, &lt;code&gt;?&lt;/code&gt;, etc..&lt;/p&gt;
&lt;p&gt;If your script depends on globbing, you obviously shouldn&#039;t set this. Instead, you may find
&lt;a href=&quot;http://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html&quot;&gt;shopt -s failglob&lt;/a&gt;
useful, which causes globs that don&#039;t get expanded to cause errors, rather than getting
passed to the command with the &lt;code&gt;*&lt;/code&gt; intact.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html&quot;&gt;set -o pipefail&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;set -o pipefail&lt;/code&gt; causes a pipeline (for example, &lt;code&gt;curl -s http://sipb.mit.edu/ | grep foo&lt;/code&gt;) to produce a failure return code if any command errors. Normally, pipelines only return a failure if the last command errors. In combination with &lt;code&gt;set -e&lt;/code&gt;, this will make your script exit if any command in a pipeline errors.&lt;/p&gt;
&lt;h2 id=&quot;Quote+liberally&quot; name=&quot;Quote+liberally&quot;&gt;Quote liberally&lt;/h2&gt;
&lt;p&gt;Whenever you pass a variable to a command, you should probably quote it. Otherwise, the shell
will perform
&lt;a href=&quot;http://www.gnu.org/software/bash/manual/html_node/Word-Splitting.html&quot;&gt;word-splitting&lt;/a&gt; and
&lt;a href=&quot;http://www.gnu.org/software/bash/manual/html_node/Filename-Expansion.html&quot;&gt;globbing&lt;/a&gt;,
which is likely not what you want.&lt;/p&gt;
&lt;p&gt;For example, consider the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;alex@kronborg tmp [15:23] $ dir=&quot;foo bar&quot;
alex@kronborg tmp [15:23] $ ls $dir
ls: cannot access foo: No such file or directory
ls: cannot access bar: No such file or directory
alex@kronborg tmp [15:23] $ cd &quot;$dir&quot;
alex@kronborg foo bar [15:25] $ file=*.txt
alex@kronborg foo bar [15:26] $ echo $file
bar.txt foo.txt
alex@kronborg foo bar [15:26] $ echo &quot;$file&quot;
*.txt&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Depending on what you are doing in your script, it is likely that the word-splitting and
globbing shown above are not what you expected to have happen. By using &lt;code&gt;&quot;$foo&quot;&lt;/code&gt; to access
the contents of the &lt;code&gt;foo&lt;/code&gt; variable instead of just &lt;code&gt;$foo&lt;/code&gt;, this problem does not arise.&lt;/p&gt;
&lt;p&gt;When writing a wrapper script, you may wish pass along all the arguments your script
received. Do that with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;wrapped-command &quot;$@&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See
&lt;a href=&quot;http://www.gnu.org/software/bash/manual/html_node/Special-Parameters.html&quot;&gt;&amp;quot;Special Parameters&amp;quot; in the bash manual&lt;/a&gt;
for details on the distinction between &lt;code&gt;$*&lt;/code&gt;, &lt;code&gt;$@&lt;/code&gt;, and &lt;code&gt;&quot;$@&quot;&lt;/code&gt; - the first and second are
rarely what you want in a safe shell script.&lt;/p&gt;
&lt;h2 id=&quot;Passing+filenames+or+other+positional+arguments+to+commands&quot; name=&quot;Passing+filenames+or+other+positional+arguments+to+commands&quot;&gt;Passing filenames or other positional arguments to commands&lt;/h2&gt;
&lt;p&gt;If you get filenames from the user or from shell globbing, or any other kind of
positional arguments, you should be aware that those could start with a &lt;code&gt;&quot;-&quot;&lt;/code&gt;. Even if you
quote correctly, this may still act differently from what you intended. For example,
consider a script that allows somebody to run commands as &lt;code&gt;nobody&lt;/code&gt; (exposed over &lt;code&gt;remctl&lt;/code&gt;,
perhaps), consisting of just &lt;code&gt;sudo -u nobody &quot;$@&quot;&lt;/code&gt;. The quoting is fine, but if a user
passes &lt;code&gt;-u root reboot&lt;/code&gt;, &lt;code&gt;sudo&lt;/code&gt; will catch the second &lt;code&gt;-u&lt;/code&gt; and run it as &lt;code&gt;root&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Fixing this depends on what command you&#039;re running.&lt;/p&gt;
&lt;p&gt;For many commands, however, &lt;code&gt;--&lt;/code&gt; is accepted to indicate that any options are done,
and future arguments should be parsed as positional parameters - even if they look like
options. In the &lt;code&gt;sudo&lt;/code&gt; example above, &lt;code&gt;sudo -u nobody -- &quot;$@&quot;&lt;/code&gt; would avoid this attack
(though obviously specifying in the &lt;code&gt;sudo&lt;/code&gt; configuration that commands can only be run
as &lt;code&gt;nobody&lt;/code&gt; is also a good idea).&lt;/p&gt;
&lt;p&gt;Another approach is to prefix each filename with &lt;code&gt;./&lt;/code&gt;, if the filenames are expected to be in the current directory.&lt;/p&gt;
&lt;h2 id=&quot;Temporary+files&quot; name=&quot;Temporary+files&quot;&gt;Temporary files&lt;/h2&gt;
&lt;p&gt;A common convention to create temporary file names is to use &lt;code&gt;something.$$&lt;/code&gt;.  This is not
safe.  It is better to use &lt;code&gt;mktemp&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;Other+resources&quot; name=&quot;Other+resources&quot;&gt;Other resources&lt;/h2&gt;
&lt;p&gt;Google has a &lt;a href=&quot;https://google.github.io/styleguide/shell.xml&quot;&gt;Shell Style Guide&lt;/a&gt;.
As the name suggests, it primarily focuses on good style, but some items are
safety/security-relevant.&lt;/p&gt;
&lt;h2 id=&quot;Conclusion&quot; name=&quot;Conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;When possible, instead of writing a &amp;quot;safe&amp;quot; shell script, &lt;strong&gt;use a higher-level language
like Python&lt;/strong&gt;. If you can&#039;t do that, the shell has several options that you can enable that
will reduce your chances of having bugs, and you should be sure to quote liberally.&lt;/p&gt;
&lt;p&gt;Source &lt;a href=&quot;https://sipb.mit.edu/doc/safe-shell/&quot;&gt;Writing Safe Shell&lt;/a&gt;.&lt;/p&gt;</content>
</entry>
<entry>
<title>editor to replace emacs</title>
<link href="https://www.0ink.net/posts/2016/2016-11-15-editor-evaluation.html"></link>
<id>urn:uuid:542f86f5-e4b7-1aed-8d56-4f16385b2227</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[At the end, I switched to geany
GUI

TextAdept
Bluefish Editor
editra
...]]></summary>
<content type="html">&lt;p&gt;At the end, I switched to &lt;a href=&quot;http://www.geany.org/&quot;&gt;geany&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;GUI&quot; name=&quot;GUI&quot;&gt;GUI&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://foicica.com/textadept/&quot;&gt;TextAdept&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://bluefish.openoffice.nl/index.html&quot;&gt;Bluefish Editor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://editra.org/&quot;&gt;editra&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wiki.gnome.org/Apps/Gedit&quot;&gt;gedit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Console&quot; name=&quot;Console&quot;&gt;Console&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.jbox.dk/sanos/editor.htm&quot;&gt;sanos editor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/lanurmi/efte&quot;&gt;eFTE&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://os.ghalkes.nl/tilde/&quot;&gt;Tilde&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;TCL%2FTK&quot; name=&quot;TCL%2FTK&quot;&gt;TCL/TK&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://tke.sourceforge.net/&quot;&gt;TKE&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://mooedit.sourceforge.net/&quot;&gt;moodit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sites.google.com/site/msedit/home&quot;&gt;msedit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Windows+only&quot; name=&quot;Windows+only&quot;&gt;Windows only&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://notepad-plus-plus.org/&quot;&gt;nodepad++&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Crimson or Emerald Editors&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Macros&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Split Views&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Interactive search&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;File Browser?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Smart indent&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Parenthesis matching&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Syntax: PHP, Markdown, C, Java, JavaScript, HTML, C++&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;UTF8&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Key Bindings: &lt;a href=&quot;http://zzyxx.wikidot.com/key-bindings&quot;&gt;bindings&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Other CUA stuff: &lt;a href=&quot;https://ergoemacs.github.io/cua-conflict.html&quot;&gt;ergoemacs&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Others:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;scite &lt;a href=&quot;http://www.scintilla.org/SciTEDownload.html&quot;&gt;Download&lt;/a&gt;
It now has a single file exe for Windows.&lt;/li&gt;
&lt;li&gt;editra&lt;/li&gt;
&lt;li&gt;notepadqq&lt;/li&gt;
&lt;li&gt;Geany&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.scintilla.org/SciTE.html&quot;&gt;Scintilla&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;curses based: &lt;a href=&quot;http://foicica.com/scinterm/&quot;&gt;scinterm&lt;/a&gt;&lt;br /&gt;
includes &lt;em&gt;jinx&lt;/em&gt; which is an example for it.&lt;/li&gt;
&lt;li&gt;SciTE - the default for Win and Linux.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.scintilla.org/ScintillaRelated.html&quot;&gt;Others&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://tke.sourceforge.net/index.html&quot;&gt;http://tke.sourceforge.net/index.html&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;TCL based.  Can we use cdk?  Can it be use in linux and windows?&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/tihirvon/dex&quot;&gt;dex&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Notes&quot; name=&quot;Notes&quot;&gt;Notes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;GUI and TUI, Linux and Windows&lt;/li&gt;
&lt;li&gt;Modeless&lt;/li&gt;
&lt;li&gt;Syntax highlighting&lt;/li&gt;
&lt;li&gt;&amp;quot;Compact&amp;quot;?&lt;/li&gt;
&lt;li&gt;Key recording macros&lt;/li&gt;
&lt;li&gt;Split windows&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Emacs+tips&quot; name=&quot;Emacs+tips&quot;&gt;Emacs tips&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://ergoemacs.org/emacs/emacs_make_modern.html&quot;&gt;make emacs modern&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://superuser.com/questions/122119/locate-all-emacs-autosaves-and-backups-in-one-folder&quot;&gt;single folder autosaves&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://emacsredux.com/blog/2013/05/09/keep-backup-and-auto-save-files-out-of-the-way/&quot;&gt;backups out of the way&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://xenon.stanford.edu/~manku/emacs.html&quot;&gt;emacs tips&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Mote+ideas%3A&quot; name=&quot;Mote+ideas%3A&quot;&gt;Mote ideas:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.emacswiki.org/emacs/LinkdMode&quot;&gt;LinkdMode&lt;/a&gt; Paired with &amp;quot;deft&amp;quot;?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;iMenu: &lt;code&gt;M-x imenu&lt;/code&gt; or:&lt;br /&gt;
&lt;code&gt;(add-hook &#039;c-mode-hook &#039;imenu-add-menubar-index)&lt;/code&gt;&lt;br /&gt;
Start typing or use TAB completion to find function defintions.
See &lt;a href=&quot;http://www.emacswiki.org/cgi-bin/wiki/ImenuMode&quot;&gt;imenuMode&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.emacswiki.org/emacs/PredictiveMode&quot;&gt;Predictive Mode&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Record, play, re-play:&lt;br /&gt;
&lt;code&gt;(global-set-key [f10]  &#039;start-kbd-macro)&lt;/code&gt;&lt;br /&gt;
&lt;code&gt;(global-set-key [f11]  &#039;end-kbd-macro)&lt;/code&gt;&lt;br /&gt;
&lt;code&gt;(global-set-key [f12]  &#039;call-last-kbd-macro)&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Selective display:&lt;br /&gt;
&lt;code&gt;M-1 C-x $&lt;/code&gt; to activate&lt;br /&gt;
&lt;code&gt;C-x $&lt;/code&gt; to go back&lt;br /&gt;
Or create shortcuts:&lt;/p&gt;
&lt;p&gt;(defun jao-toggle-selective-display ()
(interactive)
(set-selective-display (if selective-display nil 1)))
(global-set-key [f1] &#039;jao-toggle-selective-display)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(setq cua-enable-cua-keys nil)
(setq cua-highlight-region-shift-only t) ;; no transient mark mode
(setq cua-toggle-set-mark nil) ;; original set-mark behavior, i.e. no transient-mark-mode
(cua-mode)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>MariaDB Quickest Quick start</title>
<link href="https://www.0ink.net/posts/2016/2016-11-14-mariadb-quickest-quick-start.html"></link>
<id>urn:uuid:9958b877-8083-6a76-3b29-86d1259b1fef</id>
<updated>2022-01-10T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This article outlines the bare minimum to get
a MariaDB or MySQL database up and running.
It covers a CentOS/RHEL and an ArchLinux installs.
Make sure your system is up to date:


...]]></summary>
<content type="html">&lt;p&gt;This article outlines the bare minimum to get
a MariaDB or MySQL database up and running.&lt;/p&gt;
&lt;p&gt;It covers a CentOS/RHEL and an ArchLinux installs.&lt;/p&gt;
&lt;p&gt;Make sure your system is up to date:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CentOS/RHEL&lt;/th&gt;
&lt;th&gt;ArchLinux&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;yum update -y&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pacman -Syu&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Install the software:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CentOS/RHEL&lt;/th&gt;
&lt;th&gt;ArchLinux&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;yum install mariadb-server&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pacman -S mariadb&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mysql_install_db --user=mysql --basedir=/usr --datadir=/var/lib/mysql&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Start the database service:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; systemctl start mariadb&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check if it is running:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; systemctl is-active mariadb.service
 systemctl status mariadb&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The following step is optional but highly recommended:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; mysql_secure_installation&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Enable database to start on start-up:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; systemctl enable mariadb&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Enter SQL:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; mysql -u root -p&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Creating database:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; create database bugzilla;
 FLUSH PRIVILEGES;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create user:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; GRANT ALL PRIVILEGES ON bugzilla.* TO &#039;warren&#039;@&#039;localhost&#039; IDENTIFIED BY &#039;mypass&#039;;
 GRANT ALL PRIVILEGES ON killrate.* TO &#039;pocketmine&#039;@&#039;%&#039; IDENTIFIED BY &#039;mypass&#039;;
 FLUSH PRIVILEGES;&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>Jaxon: Call PHP classes from JavaScript using AJAX</title>
<link href="https://www.0ink.net/posts/2016/2016-11-14-jaxon-call-php-classes-from-javascript-using-ajax.html"></link>
<id>urn:uuid:cd7082fa-7d88-064e-b57d-ca423ab2d41e</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Jaxon is an open source PHP library for easily creating Ajax web applications. It allows into a web page to make direct Ajax calls to PHP classes that will in turn update its content, without reloading the entire page.
Jaxon implements a complete set of PHP functions to define the contents and properties of the web page. Several plugins exist to extend its functionalities and provide integration with various PHP frameworks and CMS.
...]]></summary>
<content type="html">&lt;p&gt;&lt;a href=&quot;https://www.jaxon-php.org/&quot; title=&quot;Jaxon PHP library&quot;&gt;Jaxon&lt;/a&gt; is an open source PHP library for easily creating Ajax web applications. It allows into a web page to make direct Ajax calls to PHP classes that will in turn update its content, without reloading the entire page.&lt;/p&gt;
&lt;p&gt;Jaxon implements a complete set of PHP functions to define the contents and properties of the web page. Several plugins exist to extend its functionalities and provide integration with various PHP frameworks and CMS.&lt;/p&gt;</content>
</entry>
<entry>
<title>Building chroots with yum</title>
<link href="https://www.0ink.net/posts/2016/2016-11-14-building-chroots-with-yum.html"></link>
<id>urn:uuid:2cbd831d-e896-38b2-ab77-b4d1f6d7bfb3</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Building CHROOTs with Yum in a single command:
yum --releasever=7 --installroot=/chroot/jail2 -y install httpd
Will install httpd with all its dependancies.  If you are on x86_64 and want a 32 bit chroot:
setarch i386 yum --releasever=6 --installroot=/chroot/jail32 -y install httpd
...]]></summary>
<content type="html">&lt;p&gt;Building CHROOTs with Yum in a single command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yum --releasever=7 --installroot=/chroot/jail2 -y install httpd&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Will install httpd with all its dependancies.  If you are on x86_64 and want a 32 bit chroot:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;setarch i386 yum --releasever=6 --installroot=/chroot/jail32 -y install httpd&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>My WordPress plugins</title>
<link href="https://www.0ink.net/posts/2016/2016-11-07-my-wordpress-plugins.html"></link>
<id>urn:uuid:8454e18f-26c3-24b1-1c59-9996b5e8e663</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[For my own purposes I have written a number of WordPress plugins.

S3Copy - Makes backup copies of your pictures to an S3
Compatible server.  I use sirv.com myself.  It also mangles  tags so
files are server from the S3 bucket.
wptools - A collection of WordPress related functionality.
...]]></summary>
<content type="html">&lt;p&gt;For my own purposes I have written a number of WordPress plugins.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/iliu-net/S3Copy&quot;&gt;S3Copy&lt;/a&gt; - Makes backup copies of your pictures to an S3
Compatible server.  I use &lt;a href=&quot;http://sirv.com&quot;&gt;sirv.com&lt;/a&gt; myself.  It also mangles &lt;img /&gt; tags so
files are server from the S3 bucket.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/iliu-net/wptools&quot;&gt;wptools&lt;/a&gt; - A collection of WordPress related functionality.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/iliu-net/auto-content&quot;&gt;auto-content&lt;/a&gt; - A basic post from template plugin.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/iliu-net/simple-members-only&quot;&gt;simple-members-only&lt;/a&gt; - A fork of a defunct plugin.&lt;/li&gt;
&lt;/ol&gt;</content>
</entry>
<entry>
<title>vr starting points</title>
<link href="https://www.0ink.net/posts/2016/2016-11-06-vr-startup.html"></link>
<id>urn:uuid:65aaafbe-7a77-2778-959c-5c0db67dc757</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[
vr boilerplate
threejs
vr chrome experimets
google cardboard
google cardboard
...]]></summary>
<content type="html">&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/borismus/webvr-boilerplate&quot;&gt;vr boilerplate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://virtualrealitypop.com/experimenting-with-threejs-for-virtual-reality-and-google-cardboard-86e67ba31b1c#.6xm3h9kyj&quot;&gt;threejs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://vr.chromeexperiments.com/&quot;&gt;vr chrome experimets&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.sitepoint.com/filtering-reality-with-javascript-google-cardboard/&quot;&gt;google cardboard&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.sitepoint.com/bringing-vr-to-web-google-cardboard-three-js/&quot;&gt;google cardboard&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://opensource.com/life/16/11/build-virtual-reality-app-linux&quot;&gt;open source linux vr app&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;WebGL frameworks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://biz.turbulenz.com/developers&quot;&gt;Native support?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://hexgl.bkcore.com/&quot;&gt;threejs game&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Could it be converted to WebVR?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://playcanvas.com/&quot;&gt;OpenSource Engine with Proprietary on-line dev tools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://aframe.io/&quot;&gt;Targetted for WebVR&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://babylonjs.com/&quot;&gt;TypeScript?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Hosting WordPress on OpenShift</title>
<link href="https://www.0ink.net/posts/2016/2016-10-29-hosting-wordpress-on-openshift.html"></link>
<id>urn:uuid:0a198bc3-0a1a-59a7-fd2f-2fbc728bbbc9</id>
<updated>2022-01-10T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[
So I finally moved my WordPress web sites to OpenShift.
OpenShift is a cloud based Platform-as-a-Service offering from RedHat.   And while there is a learning curve I would say that so far it works great.
My implementation is a fully cloud based solution. Makes use of the following services:

GitHub for code hosting
...]]></summary>
<content type="html">&lt;p&gt;&lt;img src=&quot;/images/2016/img_0423.jpg&quot; alt=&quot;openshift&quot; /&gt;&lt;/p&gt;
&lt;p&gt;So I finally moved my WordPress web sites to OpenShift.&lt;/p&gt;
&lt;p&gt;OpenShift is a cloud based Platform-as-a-Service offering from RedHat.   And while there is a learning curve I would say that so far it works great.&lt;/p&gt;
&lt;p&gt;My implementation is a fully cloud based solution. Makes use of the following services:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GitHub for code hosting&lt;/li&gt;
&lt;li&gt;Travis-CI for continuous integration.&lt;/li&gt;
&lt;li&gt;OpenShift (with autoscaling) for the database and web server&lt;/li&gt;
&lt;li&gt;CloudFlare&lt;/li&gt;
&lt;li&gt;Sirv.com for image hosting&lt;/li&gt;
&lt;li&gt;Facebook and G+ integration&lt;/li&gt;
&lt;li&gt;Google drive for cloud backups&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All the code can be examined on GitHub.&lt;/p&gt;
&lt;p&gt;For the WordPress hosting I started with the OpenShift WordPress QuickStart and added scripts to deploy directly from Github to OpenShift via Travis.&lt;/p&gt;
&lt;p&gt;Actually Travis has that functionality built in but it was a little quirky for my use cases so I wrote my own.&lt;/p&gt;
&lt;p&gt;On the OpenShift side, I added code to download add-ons (plugins and themes) automatically and to deploy from the same repo to multiple apps.&lt;/p&gt;
&lt;p&gt;The rationale for this is to get addons installed automatically in the
event of autoscaling while keeping the github commit log fairly tidy.&lt;/p&gt;
&lt;p&gt;Also created a couple of Wordpress plugins to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Misc shortcodes and stuff&lt;/li&gt;
&lt;li&gt;Automatically upload pictures to an S3 cloud storage (sivr.com in my case but this is configurable)&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>CSR ideas</title>
<link href="https://www.0ink.net/posts/2016/2016-10-14-csr-ideas.html"></link>
<id>urn:uuid:f4b1f382-871c-3b1a-eaa6-70821e6814c5</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Work improvements
NFR

Javascript single page application

JS GUI
...]]></summary>
<content type="html">&lt;p&gt;Work improvements&lt;/p&gt;
&lt;h2 id=&quot;NFR&quot; name=&quot;NFR&quot;&gt;NFR&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Javascript single page application
&lt;ul&gt;
&lt;li&gt;JS GUI&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Retargettable back-end:
&lt;ul&gt;
&lt;li&gt;Local&lt;/li&gt;
&lt;li&gt;Remote&lt;/li&gt;
&lt;li&gt;Synchronization utility&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Multi-user (authentication?)&lt;/li&gt;
&lt;li&gt;Output Excel&lt;/li&gt;
&lt;li&gt;Output changes&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;FR&quot; name=&quot;FR&quot;&gt;FR&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;requirements&lt;/li&gt;
&lt;li&gt;roadmap objects
&lt;ul&gt;
&lt;li&gt;release time lines&lt;/li&gt;
&lt;li&gt;indicators&lt;/li&gt;
&lt;li&gt;descriptive text&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Meta data
&lt;ul&gt;
&lt;li&gt;attributes (i.e owner, reviewers, etc)&lt;/li&gt;
&lt;li&gt;versioning&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Milestone data&lt;/li&gt;
&lt;li&gt;Detail data
&lt;ul&gt;
&lt;li&gt;release details&lt;/li&gt;
&lt;li&gt;line items&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>game lists</title>
<link href="https://www.0ink.net/posts/2016/2016-10-12-games-list.html"></link>
<id>urn:uuid:192615cd-0071-9037-85df-af82318d53d6</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[
Cybernator
Darius Twin
Another World | Out of this World
Front Mission Series
Strike Gunner
...]]></summary>
<content type="html">&lt;ul&gt;
&lt;li&gt;Cybernator&lt;/li&gt;
&lt;li&gt;Darius Twin&lt;/li&gt;
&lt;li&gt;Another World | Out of this World&lt;/li&gt;
&lt;li&gt;Front Mission Series&lt;/li&gt;
&lt;li&gt;Strike Gunner&lt;/li&gt;
&lt;li&gt;The Legend ...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Super Bomberman for about 59.99 (dollars) but later it was also sold alone for approximately 29.95.&lt;/p&gt;
&lt;p&gt;Multitap compatible games:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Barkley: Shut Up and Jam!, Bill Walsh College Football, College Slam, Elite Soccer, ESPN National Hockey Night, FIFA International Soccer, FIFA &#039;96, Firestriker, Hammerlock Wrestling, Head On Soccer, J-League Soccer, Looney Toons B-Ball, Lord of the Rings, Madden &#039;94, Madden &#039;95, Madden &#039;96, Madden &#039;97, Micro Machines, Natsume Championship Wrestling, NBA Give &#039;n Go, NBA Jam, NBA Jam TE, NBA Live 95, NBA Live 96, NBA Live 97, NCAA Final Four, NCAA Football, NHL &#039;94, NHL &#039;95, NHL &#039;96, NHL &#039;97, Olympic Summer Games, Peace Keepers, Pieces, Rap Jam Vol. 1, Saturday Night Slam Masters, Secret of Mana, Slam Dunk TV Animation (Japanese), Soccer Shootout, Sporting News: Power Baseball, Sterling Silver: End 2 End, Street Hockey &#039;95, Street Racer, Super Bomberman 1, Super Bomberman 2, Super Bomberman 3, Super Bomberman 4 (Japanese), Super Bomberman 5 (Japanese), Super Tetris 3 (Japanese), Tiny Toons Wacky Sports, Virtual Soccer (Japanese), Top Gear 3000, WWF Raw. (Puh!).&lt;/li&gt;
&lt;li&gt;Peacekeepers&lt;/li&gt;
&lt;li&gt;Secret of Mana&lt;/li&gt;
&lt;li&gt;Firestriker&lt;/li&gt;
&lt;li&gt;Bakukyuu Renpatsu!! Super B-Daman: Port 2&lt;/li&gt;
&lt;li&gt;Bakutou Dochers: Port 2&lt;/li&gt;
&lt;li&gt;Barkley Shut Up and Jam!: Port 2&lt;/li&gt;
&lt;li&gt;Battle Cross: Port 2&lt;/li&gt;
&lt;li&gt;Battle Jockey: Port 2&lt;/li&gt;
&lt;li&gt;Bill Walsh College Football: Port 2&lt;/li&gt;
&lt;li&gt;Capcom&#039;s Soccer Shootout: Port 2&lt;/li&gt;
&lt;li&gt;College Slam: Port 2&lt;/li&gt;
&lt;li&gt;Crystal Beans From Dungeon Explorer: Port 2&lt;/li&gt;
&lt;li&gt;Dragon - The Bruce Lee Story: Port 2&lt;/li&gt;
&lt;li&gt;Dream Basketball - Dunk and Hoop: Port 2&lt;/li&gt;
&lt;li&gt;Dynamic Stadium: Port 2&lt;/li&gt;
&lt;li&gt;ESPN National Hockey Night: Port 2&lt;/li&gt;
&lt;li&gt;FIFA 98: Port 2&lt;/li&gt;
&lt;li&gt;FIFA International Soccer: Port 2&lt;/li&gt;
&lt;li&gt;FIFA Soccer 96: Port 2&lt;/li&gt;
&lt;li&gt;FIFA Soccer 97: Port 2&lt;/li&gt;
&lt;li&gt;Final Set: Port 2&lt;/li&gt;
&lt;li&gt;Fire Striker: Port 2&lt;/li&gt;
&lt;li&gt;From TV Animation Slam Dunk - SD Heat Up!!: Port 2&lt;/li&gt;
&lt;li&gt;Go! Go! Dodge League: Port 2&lt;/li&gt;
&lt;li&gt;Hammerlock Wrestling: Port 2&lt;/li&gt;
&lt;li&gt;Hat Trick Hero 2: Port 2&lt;/li&gt;
&lt;li&gt;Head-On Soccer: Port 2&lt;/li&gt;
&lt;li&gt;Hebereke no Oishii Puzzle ha Irimasenka: Port 2&lt;/li&gt;
&lt;li&gt;Human Grand Prix III - F1 Triple Battle: Port 2&lt;/li&gt;
&lt;li&gt;Human Grand Prix IV - F1 Dream Battle: Port 2&lt;/li&gt;
&lt;li&gt;Hungry Dinosaurs: Port 2&lt;/li&gt;
&lt;li&gt;International Superstar Soccer Deluxe: Port 2&lt;/li&gt;
&lt;li&gt;J. League Excite Stage &#039;94: Port 2&lt;/li&gt;
&lt;li&gt;J. League Excite Stage &#039;95: Port 2&lt;/li&gt;
&lt;li&gt;J. League Excite Stage &#039;96: Port 2&lt;/li&gt;
&lt;li&gt;J. League Super Soccer &#039;95: Port 2&lt;/li&gt;
&lt;li&gt;J. League Super Soccer: Port 2&lt;/li&gt;
&lt;li&gt;JWP Joshi Pro Wrestling - Pure Wrestle Queens: Port 2&lt;/li&gt;
&lt;li&gt;Jikkyou Power Pro Wrestling &#039;96: Port 2&lt;/li&gt;
&lt;li&gt;Jimmy Connors Pro Tennis Tour: Port 2&lt;/li&gt;
&lt;li&gt;Kunio-kun no Dodge Ball dayo Zenin Shuugou!: Port 2&lt;/li&gt;
&lt;li&gt;Looney Tunes Basketball: Port 2&lt;/li&gt;
&lt;li&gt;Madden NFL &#039;94: Port 2&lt;/li&gt;
&lt;li&gt;Madden NFL &#039;95: Port 2&lt;/li&gt;
&lt;li&gt;Madden NFL &#039;96: Port 2&lt;/li&gt;
&lt;li&gt;Madden NFL &#039;97: Port 2&lt;/li&gt;
&lt;li&gt;Madden NFL &#039;98: Port 2&lt;/li&gt;
&lt;li&gt;Micro Machines 2 - Turbo Tournament: Port 2&lt;/li&gt;
&lt;li&gt;Micro Machines: Port 2&lt;/li&gt;
&lt;li&gt;Mizuki Shigeru no Youkai Hyakkiyakou: Port 2&lt;/li&gt;
&lt;li&gt;Multi Play Volleyball: Port 2&lt;/li&gt;
&lt;li&gt;NBA Give &#039;N Go: Port 2&lt;/li&gt;
&lt;li&gt;NBA Hang Time: Port 2&lt;/li&gt;
&lt;li&gt;NBA Jam - Tournament Edition: Port 2&lt;/li&gt;
&lt;li&gt;NBA Jam: Port 2&lt;/li&gt;
&lt;li&gt;NBA Live 95: Port 2&lt;/li&gt;
&lt;li&gt;NBA Live 96: Port 2&lt;/li&gt;
&lt;li&gt;NBA Live 97: Port 2&lt;/li&gt;
&lt;li&gt;NBA Live 98: Port 2&lt;/li&gt;
&lt;li&gt;NCAA Final Four Basketball: Port 2&lt;/li&gt;
&lt;li&gt;NCAA Football: Port 2&lt;/li&gt;
&lt;li&gt;NFL Quarterback Club 96: Port 2&lt;/li&gt;
&lt;li&gt;NFL Quarterback Club: Port 2&lt;/li&gt;
&lt;li&gt;NHL &#039;94: Port 2&lt;/li&gt;
&lt;li&gt;NHL &#039;98: Port 2&lt;/li&gt;
&lt;li&gt;NHL Pro Hockey &#039;94: Port 2&lt;/li&gt;
&lt;li&gt;Natsume Championship Wrestling: Port 2&lt;/li&gt;
&lt;li&gt;Peace Keepers, The: Port 2&lt;/li&gt;
&lt;li&gt;Pieces: Port 2&lt;/li&gt;
&lt;li&gt;Rap Jam - Volume One: Port 2&lt;/li&gt;
&lt;li&gt;Saturday Night Slam Masters: Port 2&lt;/li&gt;
&lt;li&gt;Secret of Mana: Port 2&lt;/li&gt;
&lt;li&gt;Shin Nippon Pro Wrestling &#039;94 - Battlefield in Tokyo Dome: Port 2&lt;/li&gt;
&lt;li&gt;Shin Nippon Pro Wrestling - Chou Senshi in Tokyo Dome: Port 2&lt;/li&gt;
&lt;li&gt;Shin Nippon Pro Wrestling Kounin &#039;95 - Tokyo Dome Battle 7: Port 2&lt;/li&gt;
&lt;li&gt;Smash Tennis: Port 2&lt;/li&gt;
&lt;li&gt;Sporting News, The - Power Baseball: Port 2&lt;/li&gt;
&lt;li&gt;Sterling Sharpe End 2 End: Port 2&lt;/li&gt;
&lt;li&gt;Street Hockey &#039;95: Port 2&lt;/li&gt;
&lt;li&gt;Street Racer: Port 2&lt;/li&gt;
&lt;li&gt;Sugoi Hebereke: Port 2&lt;/li&gt;
&lt;li&gt;Sugoro Quest ++ Dicenics: Port 2&lt;/li&gt;
&lt;li&gt;Super Bomberman - Panic Bomber W: Port 2&lt;/li&gt;
&lt;li&gt;Super Bomberman 2: Port 2&lt;/li&gt;
&lt;li&gt;Super Bomberman 3: Port 2&lt;/li&gt;
&lt;li&gt;Super Bomberman 4: Port 2&lt;/li&gt;
&lt;li&gt;Super Bomberman 5: Port 2&lt;/li&gt;
&lt;li&gt;Super Bomberman: Port 2&lt;/li&gt;
&lt;li&gt;Super Fire Pro Wrestling - Queen&#039;s Special: Port 2&lt;/li&gt;
&lt;li&gt;Super Fire Pro Wrestling Special: Port 2&lt;/li&gt;
&lt;li&gt;Super Fire Pro Wrestling X Premium: Port 2&lt;/li&gt;
&lt;li&gt;Super Fire Pro Wrestling X: Port 2&lt;/li&gt;
&lt;li&gt;Super Formation Soccer 94 - World Cup Final Data: Port 2&lt;/li&gt;
&lt;li&gt;Super Formation Soccer 94: Port 2&lt;/li&gt;
&lt;li&gt;Super Formation Soccer 95 della Serie A - UCC Xaqua: Port 2&lt;/li&gt;
&lt;li&gt;Super Formation Soccer 95 della Serie A: Port 2&lt;/li&gt;
&lt;li&gt;Super Formation Soccer 96: Port 2&lt;/li&gt;
&lt;li&gt;Super Formation Soccer II: Port 2&lt;/li&gt;
&lt;li&gt;Super Ice Hockey: Port 2&lt;/li&gt;
&lt;li&gt;Super Kyousouba - Kaze no Sylphid: Port 2&lt;/li&gt;
&lt;li&gt;Super Power League: Port 2&lt;/li&gt;
&lt;li&gt;Super Tekkyuu Fight!: Port 2&lt;/li&gt;
&lt;li&gt;Super Tetris 3: Port 2&lt;/li&gt;
&lt;li&gt;Syndicate: Port 2&lt;/li&gt;
&lt;li&gt;Tenryu Genichiro no Pro Wrestling Revolution: Port 2&lt;/li&gt;
&lt;li&gt;Tiny Toon Adventures - Wild &amp;amp; Wacky Sports: Port 2&lt;/li&gt;
&lt;li&gt;Top Gear 3000: Port 2&lt;/li&gt;
&lt;li&gt;Turbo Toons: Port 2&lt;/li&gt;
&lt;li&gt;Virtual Soccer: Port 2&lt;/li&gt;
&lt;li&gt;Vs. Collection: Port 2&lt;/li&gt;
&lt;li&gt;WWF Raw: Port 2&lt;/li&gt;
&lt;li&gt;Yuujin no Furi Furi Girls: Port 2&lt;/li&gt;
&lt;li&gt;Zero 4 Champ RR-Z: Port 2&lt;/li&gt;
&lt;li&gt;Zero 4 Champ RR: Port 2&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;C64 notes&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.c64.wiki.com/index.php&quot;&gt;c64 wiki&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;VICE with SDL&lt;/p&gt;
&lt;p&gt;Coop games&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Goonies&lt;/li&gt;
&lt;li&gt;Realm of Impossibility&lt;/li&gt;
&lt;li&gt;ACE Air Combat Emulator&lt;/li&gt;
&lt;li&gt;Alien Syndrome&lt;/li&gt;
&lt;li&gt;Armalyte&lt;/li&gt;
&lt;li&gt;Bubble Bobble&lt;/li&gt;
&lt;li&gt;Katakis&lt;/li&gt;
&lt;li&gt;Mario Bros (Atari)&lt;/li&gt;
&lt;li&gt;Mega Apocalypse&lt;/li&gt;
&lt;li&gt;Castle s of Doctor Creep&lt;/li&gt;
&lt;li&gt;Wizball&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Head to head&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MULE&lt;/li&gt;
&lt;li&gt;Batty&lt;/li&gt;
&lt;li&gt;Bomb Squad&lt;/li&gt;
&lt;li&gt;Highlander&lt;/li&gt;
&lt;li&gt;International Soccer&lt;/li&gt;
&lt;li&gt;Pitstop II&lt;/li&gt;
&lt;li&gt;Spy vs Spy&lt;/li&gt;
&lt;li&gt;The way of the exploding fist&lt;/li&gt;
&lt;li&gt;Trailblazer&lt;/li&gt;
&lt;li&gt;Wizard of Wor&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>JavaScript resources</title>
<link href="https://www.0ink.net/posts/2016/2016-08-23-javascript-links.html"></link>
<id>urn:uuid:276c94cb-bdc8-8940-1302-cb97867e63f2</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[
Typescript
voxeljs
ige

More powerful github web pages
...]]></summary>
<content type="html">&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.codeproject.com/Articles/756189/Master-Chief-CreateJS-TypeScript&quot;&gt;Typescript&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://voxeljs.com/&quot;&gt;voxeljs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/Irrelon/ige&quot;&gt;ige&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;More powerful github web pages&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.github.com/v3/&quot;&gt;github api&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://javascriptweblog.wordpress.com/2010/11/29/json-and-jsonp/&quot;&gt;json &amp;amp; jsonp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://stackoverflow.com/questions/26416727/cross-origin-resource-sharing-on-github-pages&quot;&gt;cross origin resource sharing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Use JavaScript XHR to get data from github API  (CORS is enabled).&lt;/p&gt;
&lt;p&gt;Show:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Download counts for a project&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Latest release tag&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.typescriptlang.org/docs/tutorial.html&quot;&gt;typescript tutorial&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.devbridge.com/articles/say-hello-to-typescript/&quot;&gt;intro to typescript&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Programming 2016</title>
<link href="https://www.0ink.net/posts/2016/2016-06-06-programming-2016.html"></link>
<id>urn:uuid:12750060-607b-549b-cbe5-bf6d022cd7e0</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Programming 2016

GWT and GWT on Mobile and Java servlets

Generate Excel
http://www.gwtproject.org/overview.html
...]]></summary>
<content type="html">&lt;h2 id=&quot;Programming+2016&quot; name=&quot;Programming+2016&quot;&gt;Programming 2016&lt;/h2&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;GWT and GWT on Mobile and Java servlets
&lt;ul&gt;
&lt;li&gt;Generate Excel
&lt;a href=&quot;http://www.gwtproject.org/overview.html&quot;&gt;http://www.gwtproject.org/overview.html&lt;/a&gt;
&lt;a href=&quot;http://www.m-gwt.com/&quot;&gt;http://www.m-gwt.com/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Java based:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Game Api &lt;a href=&quot;https://libgdx.badlogicgames.com/&quot;&gt;libgdx&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Other Game lib &lt;a href=&quot;https://jmonkeyengine.org/&quot;&gt;JMonkeyEngine&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://software.intel.com/en-us/multi-os-engine&quot;&gt;MultiOS&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://j2objc.org/&quot;&gt;j2objc&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;RoboVM forks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/FlexoVM&quot;&gt;FlexoVM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;BugVM&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Swift?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;D &lt;a href=&quot;https://wiki.dlang.org/LDC&quot;&gt;status&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Programming 2015&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cross-Platform: Linux, Windows, Android, iOS, WebApp?&lt;/li&gt;
&lt;li&gt;Run-Time: &amp;gt;100MB?&lt;/li&gt;
&lt;li&gt;ease of deployment (wrap app and drops)&lt;/li&gt;
&lt;li&gt;gui programming&lt;/li&gt;
&lt;li&gt;object classes and types &lt;/li&gt;
&lt;li&gt;memory management &lt;/li&gt;
&lt;li&gt;speed&lt;/li&gt;
&lt;li&gt;skills marketability &lt;/li&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Development &lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://hyperpolyglot.org/web&quot;&gt;http://hyperpolyglot.org/web&lt;/a&gt; - comparison between TypeScript, Dart, Hack (php like)&lt;/p&gt;
&lt;p&gt;ANGULAR&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://angular.io/docs/ts/latest/quickstart.html&quot;&gt;https://angular.io/docs/ts/latest/quickstart.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://angular.io/docs/ts/latest/tutorial/&quot;&gt;https://angular.io/docs/ts/latest/tutorial/&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;TypeScript&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Headers: &lt;a href=&quot;http://definitelytyped.org/&quot;&gt;http://definitelytyped.org/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;TypeScript - compiled, optionally typed language that compiles to JavaScript&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;node-webkit - Desktop apps&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ionic framework - deploy to phone&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Frameworks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://angular.io/&quot;&gt;https://angular.io/&lt;/a&gt; - JavaScript framework for web apps&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;jQuery&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&amp;quot;app.js&amp;quot; : This is a UI library for writing mobile apps&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;TypeScript?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://blog.scottlogic.com/2014/09/10/node-webkit.html&quot;&gt;http://blog.scottlogic.com/2014/09/10/node-webkit.html&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Angular.JS? &lt;a href=&quot;https://angular.io/&quot;&gt;https://angular.io/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;React Native &lt;a href=&quot;https://facebook.github.io/react-native/&quot;&gt;https://facebook.github.io/react-native/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://appjs.com/&quot;&gt;http://appjs.com/&lt;/a&gt; - &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enyo? &lt;a href=&quot;http://enyojs.com/&quot;&gt;http://enyojs.com/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://noeticforce.com/best-hybrid-mobile-app-ui-frameworks-html5-js-css&quot;&gt;http://noeticforce.com/best-hybrid-mobile-app-ui-frameworks-html5-js-css&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;RunTimes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;NW.js&lt;/li&gt;
&lt;li&gt;electron (&lt;a href=&quot;https://github.com/atom/electron&quot;&gt;https://github.com/atom/electron&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Facebook&#039;s React Native&lt;/p&gt;
&lt;p&gt;JavaScript supersets:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TypeScript&lt;/li&gt;
&lt;li&gt;Dart&lt;/li&gt;
&lt;li&gt;CoffeeScript&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Translateable:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Google Web Toolkit (Java to JavaScript)&lt;/li&gt;
&lt;li&gt;Pyjamas (Python to Javascript)&lt;/li&gt;
&lt;li&gt;HaXe&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;Dev Notes&lt;/p&gt;
&lt;p&gt;Replacment for Make and Autoconf:
&lt;a href=&quot;https://embedthis.com/makeme/&quot;&gt;MakeMe&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;(If you don&#039;t have root but have Android 4+ you can use the command-line program adb from the Android SDK platform tools to make backups via a desktop computer)&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.chromebookhq.com/five-best-online-ides-making-the-switch-to-a-chromebook/&quot;&gt;http://www.chromebookhq.com/five-best-online-ides-making-the-switch-to-a-chromebook/&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;Dev+Tools&quot; name=&quot;Dev+Tools&quot;&gt;Dev Tools&lt;/h2&gt;
&lt;p&gt;Alternative languages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;D : better than C, but not over-the-top like C++?  Covers only Win and Linux&lt;/li&gt;
&lt;li&gt;Vala : Kinda like C# but for Gnome.  Covers Win and Linux.  (Android maybe through NDK).&lt;/li&gt;
&lt;li&gt;Java: Kinda over the top and heavy.  Covers Win and Linux.  Android yes, but different GUI library.  iOS probably yes.&lt;/li&gt;
&lt;li&gt;Python: scripting language.  Win, Linux.  Android maybe... iOS maybe...&lt;/li&gt;
&lt;li&gt;Javascript: scripting language. ALL PLATFORMS.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Other options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Python with &lt;a href=&quot;http://kivy.org/&quot;&gt;Kivy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://haxe.org/&quot;&gt;Haxe&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Build+Tools&quot; name=&quot;Build+Tools&quot;&gt;Build Tools&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;MakeKit - autotools look &amp;amp; feel but lighter&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.dervishd.net/libre-software-projects&quot;&gt;mobs&lt;/a&gt;: autoconf workalike.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Resources&quot; name=&quot;Resources&quot;&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.dervishd.net/libre-software-projects&quot;&gt;http://www.dervishd.net/libre-software-projects&lt;/a&gt;
syslogd in perl, mobom perl modules.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;My own Notes App&lt;/h1&gt;
&lt;p&gt;JumpNote      +      OI Notpad
(Background          (Tags support)
Sync)
V
Simple Note backend
V
Tags UI
(Filter, modify tags)
V
Task UI
V
Widget&lt;/p&gt;
&lt;p&gt;WebApp + Mobile Dev:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://demux.vektorsoft.com/demux/&quot;&gt;http://demux.vektorsoft.com/demux/&lt;/a&gt;
A Java framework that works on multiple platforms.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://asterclick.drclue.net/WBEA.html&quot;&gt;http://asterclick.drclue.net/WBEA.html&lt;/a&gt;
Allows for webapps on desktops&lt;/li&gt;
&lt;li&gt;PhoneGap&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.mobilexweb.com/emulators&quot;&gt;http://www.mobilexweb.com/emulators&lt;/a&gt;
Test mobile apps on desktop&lt;/li&gt;
&lt;li&gt;Javascript optimizer:
&lt;a href=&quot;https://developers.google.com/closure/&quot;&gt;https://developers.google.com/closure/&lt;/a&gt;
&lt;a href=&quot;https://github.com/mishoo/UglifyJS&quot;&gt;https://github.com/mishoo/UglifyJS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;JS Compiler: &lt;a href=&quot;https://developer.mozilla.org/en/Rhino_JavaScript_Compiler&quot;&gt;https://developer.mozilla.org/en/Rhino_JavaScript_Compiler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Java 2 JS Toolkits:
&lt;a href=&quot;http://code.google.com/webtoolkit/&quot;&gt;http://code.google.com/webtoolkit/&lt;/a&gt;
&lt;a href=&quot;http://j2s.sourceforge.net/&quot;&gt;http://j2s.sourceforge.net/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Python 2 JS Toolkigs:
&lt;a href=&quot;http://pyjs.org/&quot;&gt;http://pyjs.org/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;JS Compiler for command line:
&lt;a href=&quot;https://developers.google.com/v8/&quot;&gt;https://developers.google.com/v8/&lt;/a&gt;
&lt;a href=&quot;http://en.wikipedia.org/wiki/Nodejs&quot;&gt;http://en.wikipedia.org/wiki/Nodejs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://this-voice.org/alchemy/pride.html&quot;&gt;http://this-voice.org/alchemy/pride.html&lt;/a&gt; Compiling Android stuff&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Documentation around Syncing...&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://ericmiles.wordpress.com/2010/09/22/connecting-the-dots-with-android-syncadapter/&quot;&gt;http://ericmiles.wordpress.com/2010/09/22/connecting-the-dots-with-android-syncadapter/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://developer.android.com/resources/samples/SampleSyncAdapter/index.html&quot;&gt;http://developer.android.com/resources/samples/SampleSyncAdapter/index.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Other Notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Perki replacement that runs on Android.&lt;/li&gt;
&lt;li&gt;Use WebKit/PhoneGap + Javascript and HTML5&lt;/li&gt;
&lt;li&gt;Markdown library for Javascript&lt;/li&gt;
&lt;li&gt;Markdown editor for javscript&lt;/li&gt;
&lt;li&gt;TXGR converted to HTML5 Canvas&lt;/li&gt;
&lt;li&gt;How do we do background sync?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;More example code:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://code.google.com/p/jumpnote/&quot;&gt;http://code.google.com/p/jumpnote/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.java2s.com/Open-Source/Android/CatalogAndroid.htm&quot;&gt;http://www.java2s.com/Open-Source/Android/CatalogAndroid.htm&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We want to have it for Android, Linux and Windows.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://libreplanet.org/wiki/Group:Hardware/Howto_have_a_free_android_sdk&quot;&gt;http://libreplanet.org/wiki/Group:Hardware/Howto_have_a_free_android_sdk&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We need to research:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;* Alternative to freewrap

    * http://jsmooth.sourceforge.net/
    * http://launch4j.sourceforge.net/
    * http://www.thisiscool.com/gcc_mingw.htm

        * http://vertis.github.com/2007/06/24/native-java-with-gcj-and-swt.html

    * http://winrun4j.sourceforge.net/

* Alternative to Canvas

    * http://www.piccolo2d.org/
    * http://www.jhotdraw.org/
    * http://www.manageability.org/blog/stuff/open-source-structured-graphics-libraries-in-java&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Contains an overview of options...&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://jean-philippe.leboeuf.name/notebook/archives/000315.html&quot;&gt;http://jean-philippe.leboeuf.name/notebook/archives/000315.html&lt;/a&gt;
Another overview of options&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Which Toolkit to use (SWT, Swing, AWT, etc)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;An alternative to Eclipse for Android Development:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://freecode.com/projects/pride&quot;&gt;http://freecode.com/projects/pride&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A freewrap like tool for pythonL&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://freecode.com/projects/pyinstaller&quot;&gt;http://freecode.com/projects/pyinstaller&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;More Android Dev options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PhoneGAP&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://kivy.org/&quot;&gt;http://kivy.org/&lt;/a&gt; Python, multi platform&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://code.google.com/p/android-python27/w/list&quot;&gt;https://code.google.com/p/android-python27/w/list&lt;/a&gt; - Python on android&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Managing our personal finances</title>
<link href="https://www.0ink.net/posts/2016/2016-05-22-managing-our-personal-finances.html"></link>
<id>urn:uuid:4c85ab8a-bfbb-bacf-61b8-183c5a907cc2</id>
<updated>2022-01-10T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[During my last vacation I wanted to move how we manage our personal
finances away from the ad-hoc spreadsheet that we had been using for
the past few years. I envisioned something server side, so I wouldn't
need to add software on my wife's computer. And initial quick run
through of server side software did not yield anything that interested
me. In general I could only find full accounting applications, which
...]]></summary>
<content type="html">&lt;p&gt;During my last vacation I wanted to move how we manage our personal
finances away from the ad-hoc spreadsheet that we had been using for
the past few years. I envisioned something server side, so I wouldn&#039;t
need to add software on my wife&#039;s computer. And initial quick run
through of server side software did not yield anything that interested
me. In general I could only find &lt;em&gt;full&lt;/em&gt; accounting applications, which
would have been an over kill for personal finance/expense tracking. So
then I checked through some desktop applications. I looked at the
following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://homebank.free.fr/&quot;&gt;HomeBank&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.grisbi.org/&quot;&gt;Grisbi&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I found many others, but I only tried these two. The most common
suggestion among Open Source advocates is &lt;a href=&quot;https://www.gnucash.org/&quot;&gt;GNU Cash&lt;/a&gt;,
but I did not try that because it was too big for my modest
requirements. I installed these two but I was not able to get it to do
what I wanted. Which was be able to import transactions from my back
and entered into the the application. So I went back searching to the
web this time looking for &amp;quot;personal finance&amp;quot; instead of &amp;quot;accounting&amp;quot;
and found this web application (amongst others):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://sourceforge.net/projects/pfmgr/&quot;&gt;PFMGR&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So I installed it and was able to run it on my home server. (This was
the first of these types of application that I managed to run, so I was
initially happy). So, running it, it looked OK, had an AJAX based user
interface, etc. Did not have anything in the way to import the data
files from my bank, but since it is Open Source, I could easily make
up something for it. So I modified it to include a page to import my
bank data. This seemed to work OK. That&#039;s when the annoyances started.
&lt;strong&gt;PFMGR&lt;/strong&gt; author had an specific use case in mind, so it can track not
only money accounts but share accounts. While nice, I did not have
such investments, so that feature was unused, but it will show on the
forms (annoying). A lot of the functionality of the software was around
check reconciliation. Since I don&#039;t use checks, that is not useful for
me at all. Finally, I couldn&#039;t get the reports to work at all, and the
times that they did work, they did not give me the information that I
wanted. I figured, since this is all open source I could just add/remove
the features the way I wanted. Which curiously turned out I would remove
all the features and just keep &lt;strong&gt;PFMGR&lt;/strong&gt; as a simple CRUD application.
So I figure I might as well toss it all out and find a small PHP
Framework that could do CRUD. So I came across this tutorial:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://foysalmamun.wordpress.com/2013/03/27/fat-free-crud-with-mvc-tutorial/&quot;&gt;FatFree :: CRUD with MVC&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So it gives a gentle intro to the &lt;a href=&quot;http://fatfreeframework.com/home&quot;&gt;FatFreeFramework&lt;/a&gt;.
This was just what I was looking for. I can say for simple applications,
this is perfect. I was able to get started into my own personal finance
application. Did run into a few problems. Most of it around the fact that
Fat-Free (aka as F3), although has a very gentle learning curve, and one
can get things started very quickly, it did a few things that I was not
expecting. Well, actually, for a novice programmer, it did things right.
For somebody used to using PHP directly, I would add some code to escape
and protect against invalid inputs, F3 was doing it automatically which
caused me a few headaches until I realized what F3 was doing. My main
problem with F3 is that my schema required a wide varchar column and
that seemed to cause problem with its ORM mapper. Later I will write
a simple test case and see if I can track down the issue.&lt;/p&gt;</content>
</entry>
<entry>
<title>Starting with 3D Printing</title>
<link href="https://www.0ink.net/posts/2016/2016-03-20-starting-with-3d-printing.html"></link>
<id>urn:uuid:4422c0e0-33ba-3c40-1ed3-f2247f6e73bb</id>
<updated>2022-03-03T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[So I finally tried my hand at 3D printing. Obviously I did not buy at 3D printer. These are either quite expensive or you need to assemble them yourself, which I don't think is in my capacity level.
To get started, you first need a 3D model to print. There are several 3D models available in Thingieverse, however I actually wanted to make my own model. After all, that is the whole point of 3D printing. Custom made parts/objects that can be printed as needed.
To create a 3D model you need some 3D modelling software. For my very first model I opted for TinkerCAD. This is software that runs on the cloud that lets you create your own 3D models. This is particularly interesting because you don't need to install anything on your computer and it would essentially run on anything where a web browser runs.
For a web based application, it is quite responsive and feature-full. You can use (like me) a facebook account to sign-in.
Models can then be downloaded as an &quot;.stl&quot; file (the format used by 3D printers) or send directly to 3D printing service such as 3D Hubs.
3D Hubs, is an online 3D Printing service which facilitates transactions betwen 3D Printer owners (Hubs) and people who want to make 3D prints. Printer owners can join the platform to offer 3D printing services while customers can locate printer owners to get their 3D models printed nearby.
...]]></summary>
<content type="html">&lt;p&gt;So I finally tried my hand at 3D printing. Obviously I did not buy at 3D printer. These are either quite expensive or you need to assemble them yourself, which I don&#039;t think is in my capacity level.&lt;/p&gt;
&lt;p&gt;To get started, you first need a 3D model to print. There are several 3D models available in &lt;a href=&quot;http://www.thingiverse.com/&quot;&gt;Thingieverse&lt;/a&gt;, however I actually wanted to make my own model. After all, that is the whole point of 3D printing. Custom made parts/objects that can be printed as needed.&lt;/p&gt;
&lt;p&gt;To create a 3D model you need some 3D modelling software. For my very first model I opted for &lt;a href=&quot;https://www.tinkercad.com/&quot;&gt;TinkerCAD&lt;/a&gt;. This is software that runs on the cloud that lets you create your own 3D models. This is particularly interesting because you don&#039;t need to install anything on your computer and it would essentially run on anything where a web browser runs.&lt;/p&gt;
&lt;p&gt;For a web based application, it is quite responsive and feature-full. You can use (like me) a &lt;a href=&quot;http://www.facebook.com/&quot;&gt;facebook&lt;/a&gt; account to sign-in.&lt;/p&gt;
&lt;p&gt;Models can then be downloaded as an &amp;quot;.stl&amp;quot; file (the format used by 3D printers) or send directly to 3D printing service such as &lt;a href=&quot;https://www.3dhubs.com/&quot;&gt;3D Hubs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.3dhubs.com/&quot;&gt;3D Hubs&lt;/a&gt;, is an online 3D Printing service which facilitates transactions betwen 3D Printer owners (Hubs) and people who want to make 3D prints. Printer owners can join the platform to offer 3D printing services while customers can locate printer owners to get their 3D models printed nearby.&lt;/p&gt;
&lt;p&gt;So what I did myself is design a soap dish. The one we had in our shower was glass which fell and broke. I tried looking for a replacement in several hardware stores but came up empty. So, this was a perfect use case for 3D printing.&lt;/p&gt;
&lt;p&gt;This was the end result:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2016/soapdish.png&quot; alt=&quot;dish&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Some learning points for this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;While support material can be used to create complex shapes, the results is not as smooth as I would hope for.&lt;/li&gt;
&lt;li&gt;The soap dish was a very thigh fit in the holder in the shower. This is good, because you can make a very precision part, it also means that accurate measurements (in some cases less than 1 millimeter) are very important.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, allthough &lt;a href=&quot;https://www.tinkercad.com/&quot;&gt;TinkerCAD&lt;/a&gt; is quite usable, it is very much an entry level tool. I have since switched to using &lt;a href=&quot;http://www.artofillusion.org/&quot;&gt;Art of Illusion&lt;/a&gt; which is harder to use, but allows for larger, more complex models. Also, it allows to create shapes by specifying coordinates and sizes by typing a floating point value. This is important because you can get very accurate measurements that way (as opposed to dragging shapes with the mouse, which can&#039;t do that accurately).&lt;/p&gt;</content>
</entry>
<entry>
<title>OpenShift notes</title>
<link href="https://www.0ink.net/posts/2016/2016-03-10-openshift-notes.html"></link>
<id>urn:uuid:2cab8a03-8b2b-9690-35fe-a163ded81f8f</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[THIS IS FOR ARCHIVAL PURPOSES.  THIS IS OUT-OF-DATE
backup OpenShift
openshift getenv(USER) from OpenShift php
ssh to {user}@{app-domain} gear snapshot  &gt; file
Run gear app
OpenShift migration further notes
...]]></summary>
<content type="html">&lt;p&gt;&lt;strong&gt;THIS IS FOR ARCHIVAL PURPOSES.  THIS IS OUT-OF-DATE&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;backup+OpenShift&quot; name=&quot;backup+OpenShift&quot;&gt;backup OpenShift&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;openshift getenv(USER) from OpenShift php
ssh to {user}@{app-domain} gear snapshot  &amp;gt; file&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run gear app&lt;/p&gt;
&lt;p&gt;OpenShift migration further notes&lt;/p&gt;
&lt;p&gt;Encrypt a file using a supplied password :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ openssl enc -aes-256-cbc -salt -in file.txt -out file.txt.enc -k PASS&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Decrypt a file using a supplied password :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ openssl enc -aes-256-cbc -d -in file.txt.enc -out file.txt -k PASS&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add script to tar plugin, then rhc a SSH key into account,
ssh to account and tar x data...&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;get target directory&lt;/li&gt;
&lt;li&gt;clean-up target directory&lt;/li&gt;
&lt;li&gt;extract new contents&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Probably can use deploy as an example...&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;need to list images that have been uploaded to S3.&lt;/li&gt;
&lt;li&gt;need to convert imgsrc references...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;? wordpress filters vs hooks ?&lt;/p&gt;
&lt;p&gt;Sample stuff:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://codex.wordpress.org/Writing_a_Plugin#Saving_Plugin_Data_to_the_Database&quot;&gt;writing a plugin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.sitepoint.com/working-with-databases-in-wordpress/&quot;&gt;working with database&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://premium.wpmudev.org/blog/creating-database-tables-for-plugins/&quot;&gt;creating database&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Skeleton&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/convissor/oop-plugin-template-solution&quot;&gt;plugin template&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://wordpress.stackexchange.com/questions/44708/using-a-plugin-class-inside-a-template&quot;&gt;plugin class&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.yaconiello.com/blog/how-to-write-wordpress-plugin/&quot;&gt;how to write wordpress plugin&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;http://wordpress.stackexchange.com/questions/35931/how-can-i-edit-post-data-before-it-is-saved&quot;&gt;Mangle data when saving&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://codex.wordpress.org/Post_Types&quot;&gt;post types&lt;/a&gt;
... perhaps we add an S3 flag to the post type: Attachment&lt;/p&gt;
&lt;h2 id=&quot;WordPress&quot; name=&quot;WordPress&quot;&gt;WordPress&lt;/h2&gt;
&lt;p&gt;Standard Customizations:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Appearance
&lt;ul&gt;
&lt;li&gt;Set theme&lt;/li&gt;
&lt;li&gt;Site Identity&lt;/li&gt;
&lt;li&gt;Header &amp;amp; Background iamge&lt;/li&gt;
&lt;li&gt;Menus: Set-up top bar?&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Settings
&lt;ul&gt;
&lt;li&gt;General Settings
&lt;ol&gt;
&lt;li&gt;Membership: Not anyone can register&lt;/li&gt;
&lt;li&gt;Timezone UTC&lt;/li&gt;
&lt;li&gt;Date/Time Format&lt;/li&gt;
&lt;/ol&gt;&lt;/li&gt;
&lt;li&gt;Reading
&lt;ol&gt;
&lt;li&gt;For each article in the feed: Show full text&lt;/li&gt;
&lt;/ol&gt;&lt;/li&gt;
&lt;li&gt;Discussion
&lt;ol&gt;
&lt;li&gt;Users must be registered to comment.  Not fill out name+email&lt;/li&gt;
&lt;li&gt;Comments author must have previously approved comment&lt;/li&gt;
&lt;/ol&gt;&lt;/li&gt;
&lt;li&gt;Permalinks
&lt;ol&gt;
&lt;li&gt;Month and name&lt;/li&gt;
&lt;/ol&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Plugins&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Front Page Category
&lt;ul&gt;
&lt;li&gt;Customizer, Front Page Categories, select what to show&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Collapsing category list
&lt;ul&gt;
&lt;li&gt;Customizer, Widgets, Categories, customize...&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;bbPress
&lt;ul&gt;
&lt;li&gt;NO anonymous posting&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;WP Social Login
&lt;ul&gt;
&lt;li&gt;Bouncer&lt;/li&gt;
&lt;li&gt;Allow Username change&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Rich Revies
&lt;ul&gt;
&lt;li&gt;Integrate user accounts&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;OpenShift+Recipe&quot; name=&quot;OpenShift+Recipe&quot;&gt;OpenShift Recipe&lt;/h2&gt;
&lt;p&gt;The official deploy tool &lt;a href=&quot;https://github.com/travis-ci/dpl&quot;&gt;dpl&lt;/a&gt; does not
seem to work with secondary branches.&lt;/p&gt;
&lt;h3 id=&quot;Pre-requisistes&quot; name=&quot;Pre-requisistes&quot;&gt;Pre-requisistes&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Install git&lt;/li&gt;
&lt;li&gt;Install RHC command line
&lt;ul&gt;
&lt;li&gt;yum install epel-release&lt;/li&gt;
&lt;li&gt;yum install rubygem-rhc&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Install Travis command line
&lt;ul&gt;
&lt;li&gt;yum install epel-release&lt;/li&gt;
&lt;li&gt;yum install ruby-devel rubygem-ffi (maybe others)&lt;/li&gt;
&lt;li&gt;gem install travis -v 1.8.2 --no-rdoc --no-ri&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;A github, travis-ci and opens&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;Preparing+Repo&quot; name=&quot;Preparing+Repo&quot;&gt;Preparing Repo&lt;/h3&gt;
&lt;p&gt;This section can be skipped if we already have a github repo.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Fork &lt;a href=&quot;https://github.com/openshift/wordpress-example.git&quot;&gt;wordpress-example&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Create any additional branches as needed.&lt;/li&gt;
&lt;li&gt;Configure travis-ci by creating a basic &lt;code&gt;.travis.yml&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;language: php&lt;/li&gt;
&lt;li&gt;php:&lt;/li&gt;
&lt;li&gt;
&lt;ul&gt;
&lt;li&gt;&#039;5.4&#039;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;script: true&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Since &lt;code&gt;travis setup openshift&lt;/code&gt; doesn&#039;t work, we need to use the DIY
deploy script.  So make a copy of it.  And configure:
&lt;ul&gt;
&lt;li&gt;env:&lt;/li&gt;
&lt;li&gt;_ global:&lt;/li&gt;
&lt;li&gt;__ OPENSHIFT_USER=$username&lt;/li&gt;
&lt;li&gt;__ OPENSHIFT_SECRET=$secret
&lt;ol&gt;
&lt;li&gt;Obviously the secret should be encrypted using:
&lt;ul&gt;
&lt;li&gt;travis encrypt OPENSHIFT_SECRET=$secret [--add env.global]&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;/li&gt;
&lt;li&gt;script:&lt;/li&gt;
&lt;li&gt;
&lt;ul&gt;
&lt;li&gt;sh deploy.sh&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;diydeploy:&lt;/li&gt;
&lt;li&gt;
&lt;ul&gt;
&lt;li&gt;deploy $branch:$openshift_app ... initially empty...&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;Deploying+Repo+to+OpenShift+App&quot; name=&quot;Deploying+Repo+to+OpenShift+App&quot;&gt;Deploying Repo to OpenShift App&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Create a new Application from the Openshift
&lt;a href=&quot;https://www.openshift.com/&quot;&gt;console&lt;/a&gt;.
&lt;ul&gt;
&lt;li&gt;Use (WordPress 4)&lt;/li&gt;
&lt;li&gt;Just leave initial repo to the default&lt;/li&gt;
&lt;li&gt;Decide on scaling options.&lt;/li&gt;
&lt;li&gt;DO NOT GO THROUGH SITE INSTALL YET!&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Add the $branch:$openshift_app to the &lt;code&gt;.travis.yml&lt;/code&gt;, and push so
travis-ci will deploy.&lt;/li&gt;
&lt;li&gt;Tweak configuration:
&lt;ul&gt;
&lt;li&gt;force https through .htaccess.&lt;/li&gt;
&lt;li&gt;Enable MULTISITE (if needed)&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Enable custom domain
&lt;ul&gt;
&lt;li&gt;Create Domain Name (on DNS) and add custom domain in OpenShift&lt;/li&gt;
&lt;li&gt;Add Certificate to OpenShift (self-signed or maybe CloudFlare)
&lt;ul&gt;
&lt;li&gt;openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 3650 -nodes&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Wait for DNS to propagate&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Log-on to the site and go through installation.
&lt;ul&gt;
&lt;li&gt;Verify that URLs use https:&lt;/li&gt;
&lt;li&gt;Dashboard -&amp;gt; Settings -&amp;gt; General&lt;/li&gt;
&lt;li&gt;Verify in Permalinks that https is used.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;p&gt;Fork syncing&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;   - Clone repo
   - Configure a remote fork
     1. git remote -v
     2. git remote add upstream https://github.com/openshift/wordpress-example.git
   - Syncing a fork
     1. git fetch upstream
     2. git checkout master
     3. git merge upstream/master

      //define(&#039;DOMAIN_CURRENT_SITE&#039;, &#039;dev.iliu.net&#039;);
      if ($_SERVER[&#039;SERVER_NAME&#039;] != &#039;dev.iliu.net&#039;) {
         define(&#039;DOMAIN_CURRENT_SITE&#039;, &#039;iliu.net&#039;);
      } else {
         define(&#039;DOMAIN_CURRENT_SITE&#039;, &#039;dev.iliu.net&#039;);
      }&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;openshift+mailgun&quot; name=&quot;openshift+mailgun&quot;&gt;openshift mailgun&lt;/h2&gt;
&lt;p&gt;Success! You&#039;re signed up and we just created your sandbox server sandboxf9dbaaa2f22a49a693955138381837e7.mailgun.org&lt;/p&gt;
&lt;h2 id=&quot;Include+the+Autoloader+%28see+%26quot%3BLibraries%26quot%3B+for+install+instructions%29&quot; name=&quot;Include+the+Autoloader+%28see+%26quot%3BLibraries%26quot%3B+for+install+instructions%29&quot;&gt;Include the Autoloader (see &amp;quot;Libraries&amp;quot; for install instructions)&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;require &#039;vendor/autoload.php&#039;;
use Mailgun\Mailgun;

# Instantiate the client.
$mgClient = new Mailgun(&#039;key-xxxxxxxxxxxxxxxxxxxxxxxxx&#039;);
$domain = &quot;sandboxf9dbaaa2f22a49a693955138381837e7.mailgun.org&quot;;

# Make the call to the client.
$result = $mgClient-&amp;gt;sendMessage(&quot;$domain&quot;,
                  array(&#039;from&#039;    =&amp;gt; &#039;Mailgun Sandbox &amp;lt;postmaster@sandboxf9dbaaa2f22a49a693955138381837e7.mailgun.org&amp;gt;&#039;,
                        &#039;to&#039;      =&amp;gt; &#039;Alejandro Liu &amp;lt;alejandro_liu@hotmail.com&amp;gt;&#039;,
                        &#039;subject&#039; =&amp;gt; &#039;Hello Alejandro Liu&#039;,
                        &#039;text&#039;    =&amp;gt; &#039;Congratulations Alejandro Liu, you just sent an email with Mailgun!  You are truly awesome!  You can see a record of this email in your logs: https://mailgun.com/cp/log .  You can send up to 300 emails/day from this sandbox server.  Next, you should add your own domain so you can send 10,000 emails/month for free.&#039;));&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.openshift.com/free-paas-email-server-with-roundcube/&quot;&gt;free paas mail server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.openshift.com/email-in-the-cloud-with-mailgun/&quot;&gt;mailgun&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mailgun.com/signup?plan=free&quot;&gt;mailgun plan&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.gregjs.com/linux/2015/forwarding-mail-to-your-gmail-account-with-mailgun/&quot;&gt;forwarding with mailgun&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Windows administration from the command line</title>
<link href="https://www.0ink.net/posts/2016/2016-02-02-windows-administration-from-the-command-line.html"></link>
<id>urn:uuid:327185a6-f93d-43d1-dee6-6ab553c212c8</id>
<updated>2022-03-03T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Windows system administration is very mouse driven and to reach
all tools you need to browse through Windows explorer.
If you are like me and prefer to log on a limited privilege account and use Runas to perform admin tasks, you can open these consoles with the .msc file names.
Here is a list of admin tools with their .msc file names.

domain.msc: AD Domains and Trusts
...]]></summary>
<content type="html">&lt;p&gt;Windows system administration is very mouse driven and to reach
all tools you need to browse through Windows explorer.&lt;/p&gt;
&lt;p&gt;If you are like me and prefer to log on a limited privilege account and use Runas to perform admin tasks, you can open these consoles with the .msc file names.&lt;/p&gt;
&lt;p&gt;Here is a list of admin tools with their .msc file names.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;domain.msc: AD Domains and Trusts&lt;/li&gt;
&lt;li&gt;admgmt.msc: Active Directory Management&lt;/li&gt;
&lt;li&gt;dssite.msc: AD Sites and Serrvices&lt;/li&gt;
&lt;li&gt;dsa.msc: AD Users and Computers&lt;/li&gt;
&lt;li&gt;adsiedit.msc: ADSI Edit&lt;/li&gt;
&lt;li&gt;azman.msc: Authorization manager&lt;/li&gt;
&lt;li&gt;certsrv.msc: Certification Authority Management&lt;/li&gt;
&lt;li&gt;certtmpl.msc: Certificate Templates&lt;/li&gt;
&lt;li&gt;cluadmin.exe: Cluster Administrator&lt;/li&gt;
&lt;li&gt;compmgmt.msc: Computer Management&lt;/li&gt;
&lt;li&gt;comexp.msc: Component Services&lt;/li&gt;
&lt;li&gt;cys.exe: Configure Your Server&lt;/li&gt;
&lt;li&gt;devmgmt.msc: Device Manager&lt;/li&gt;
&lt;li&gt;dhcpmgmt.msc: DHCP Managment&lt;/li&gt;
&lt;li&gt;dfrg.msc: Disk Defragmenter&lt;/li&gt;
&lt;li&gt;diskmgmt.msc: Disk Manager&lt;/li&gt;
&lt;li&gt;dfsgui.msc: Distributed File System&lt;/li&gt;
&lt;li&gt;dnsmgmt.msc: DNS Managment&lt;/li&gt;
&lt;li&gt;eventvwr.msc: Event Viewer&lt;/li&gt;
&lt;li&gt;ciadv.msc: Indexing Service Management&lt;/li&gt;
&lt;li&gt;ipaddrmgmt.msc: IP Address Management&lt;/li&gt;
&lt;li&gt;llsmgr.exe: Licensing Manager&lt;/li&gt;
&lt;li&gt;certmgr.msc: Local Certificates Management&lt;/li&gt;
&lt;li&gt;gpedit.msc: Local Group Policy Editor&lt;/li&gt;
&lt;li&gt;secpol.msc: Local Security Settings Manager&lt;/li&gt;
&lt;li&gt;lusrmgr.msc: Local Users and Groups Manager&lt;/li&gt;
&lt;li&gt;nlbmgr.exe: Network Load balancing&lt;/li&gt;
&lt;li&gt;perfmon.msc: Performance Monitor&lt;/li&gt;
&lt;li&gt;pkiview.msc: PKI Viewer&lt;/li&gt;
&lt;li&gt;pkmgmt.msc: Public Key Managment&lt;/li&gt;
&lt;li&gt;acssnap.msc: QoS Control Management&lt;/li&gt;
&lt;li&gt;tsmmc.msc: Remote Desktops&lt;/li&gt;
&lt;li&gt;rsadmin.msc: Remote Storage Administration&lt;/li&gt;
&lt;li&gt;ntmsmgr.msc: Removable Storage&lt;/li&gt;
&lt;li&gt;ntmsoprq.msc: Removable Storage Operator Requests&lt;/li&gt;
&lt;li&gt;rrasmgmt.msc: Routing and Remote Access Manager&lt;/li&gt;
&lt;li&gt;rsop.msc: Resultant Set of Policy&lt;/li&gt;
&lt;li&gt;schmmgmt.msc: Schema management&lt;/li&gt;
&lt;li&gt;services.msc: Services Management&lt;/li&gt;
&lt;li&gt;fsmgmt.msc: Shared Folders&lt;/li&gt;
&lt;li&gt;sidwalk.msc: SID Security Migration&lt;/li&gt;
&lt;li&gt;tapimgmt.msc: Telephony Management&lt;/li&gt;
&lt;li&gt;tscc.msc: Terminal Server Configuration&lt;/li&gt;
&lt;li&gt;licmgr.exe: Terminal Server Licensing&lt;/li&gt;
&lt;li&gt;tsadmin.exe: Terminal Server Manager&lt;/li&gt;
&lt;li&gt;uddi.msc: UDDI Services Managment&lt;/li&gt;
&lt;li&gt;wmimgmt.msc: Windows Mangement Instumentation&lt;/li&gt;
&lt;li&gt;winsmgmt.msc: WINS Server manager&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Deploying Kerberos based SSO</title>
<link href="https://www.0ink.net/posts/2016/2016-01-11-deploying-kerberos-based-sso.html"></link>
<id>urn:uuid:7daa016a-f53b-0a75-af2a-fbd8a86cc458</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This article goes over how to implement Single-Sign-On
on Linux.  It goes over the integration around
the Kerberos service and the applications, like for example
FireFox.
Pre-requisites

...]]></summary>
<content type="html">&lt;p&gt;This article goes over how to implement Single-Sign-On
on Linux.  It goes over the integration around
the Kerberos service and the applications, like for example
FireFox.&lt;/p&gt;
&lt;h3 id=&quot;Pre-requisites&quot; name=&quot;Pre-requisites&quot;&gt;Pre-requisites&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Kerberos Domain Controller (KDC)&lt;/li&gt;
&lt;li&gt;User accounts in the KDC&lt;/li&gt;
&lt;li&gt;KDC based logins&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To make sure that this is working, login to your workstation using your kerberos password and use the command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;klist&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This should show your principals assigned to you.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Ticket cache: FILE:/tmp/krb5cc_XXXX_ErVb5X
Default principal: zzzz@LOCALNET

Valid starting       Expires              Service principal
01/11/2016 15:51:35  01/12/2016 15:51:34  krbtgt/LOCALNET@LOCALNET&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Configuring+Apache&quot; name=&quot;Configuring+Apache&quot;&gt;Configuring Apache&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Install any necessary modules on the server:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;yum install mod_auth_kerb&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Create a service principal for the web server (this needs to be done on the KDC.
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;kadmin.local -q &quot;addprinc -randkey HTTP/www.example.com&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Export the encpryption keys to a keytab:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;kadmin.local -q &quot;ktadd -k /tmp/http.keytab HTTP/www.example.com&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Copy &lt;code&gt;/tmp/http.keytab&lt;/code&gt; to the webserver at &lt;code&gt;/etc/httpd/http.keytab&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Set ownership and permissions:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;chmod 600 /etc/httpd/http.keytab&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chown apache /etc/httpd/http.keytab&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Enable authentication, configure this:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;AuthType Kerberos&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AuthName &quot;Acme Corporation&quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KrbMethodNegotiate on&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KrbMethodK5Passwd off&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Krb5Keytab /etc/httpd/http.keytab&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;require valid-user&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Re-start apache&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;Configure+FireFox&quot; name=&quot;Configure+FireFox&quot;&gt;Configure FireFox&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Navigate to &lt;code&gt;about:config&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Search for: &lt;code&gt;negotiate-auth&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Double click on &lt;code&gt;network.negotiate-auth.trusted-uris&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Enter hostname&#039;s, URL prefixes, etc, separated by commas. Examples:
&lt;ul&gt;
&lt;li&gt;www.example.com&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.example.com/&quot;&gt;http://www.example.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;.example.com&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It is possible to configure this setting for all users by creating a global config file:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Find configuration directory:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;rpm -q firefox -l | grep preferences&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Create a javascript file in that directory. (by convention, &lt;code&gt;autoconfig.js&lt;/code&gt;; other file names will work, but for best results it should be early in the alphabet.)&lt;/li&gt;
&lt;li&gt;Add the following line:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pref(&quot;network.negotiate-auth.trusted-uris&quot;,&quot;.example.com&quot;);&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;Configure+OpenSSH+server&quot; name=&quot;Configure+OpenSSH+server&quot;&gt;Configure OpenSSH server&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Create a service principal for the host (this needs to be done on the KDC.
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;kadmin.local -q &quot;addprinc -randkey host/shell.example.com&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Export the encpryption keys to a keytab:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;kadmin.local -q &quot;ktadd -k /tmp/krb5.keytab host/shell.example.com&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Copy &lt;code&gt;/tmp/krb5.keytab&lt;/code&gt; to the host at: &lt;code&gt;/etc/krb5.keytab&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Set ownership and permissions:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;chmod 600 /etc/krb5.keytab&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chown root /etc/krb5.keytab&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Enable authentication, change these settings in &lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;KerberosAuthentication yes&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GSSAPIAuthentication yes&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GSSAPICleanupCredentials yes&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UsePAM no&lt;/code&gt; &lt;em&gt;# This is not supported by RHEL7 and should be left as &lt;code&gt;yes&lt;/code&gt;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Restart &lt;code&gt;sshd&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;Configure+OpenSSH+clients&quot; name=&quot;Configure+OpenSSH+clients&quot;&gt;Configure OpenSSH clients&lt;/h3&gt;
&lt;p&gt;Configure &lt;code&gt;/etc/ssh_config&lt;/code&gt; or &lt;code&gt;~/ssh/ssh_config&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Host *.localnet
  GSSAPIAuthentication yes
  GSSAPIDelegateCredentials yes&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>clipping ideas</title>
<link href="https://www.0ink.net/posts/2015/2015-12-11-clipping-notes.html"></link>
<id>urn:uuid:2f1cfc69-72cf-5bf5-8b70-ffccb95cb860</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[
Divide into

Work : Only visible to company and clients
Personal: Public/Private areas

...]]></summary>
<content type="html">&lt;ul&gt;
&lt;li&gt;Divide into
&lt;ul&gt;
&lt;li&gt;Work : Only visible to company and clients&lt;/li&gt;
&lt;li&gt;Personal: Public/Private areas&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Send e-mail to address =&amp;gt; creates entry&lt;/li&gt;
&lt;li&gt;handle attachments&lt;/li&gt;
&lt;li&gt;Rich text support?&lt;/li&gt;
&lt;li&gt;Markdown through short codes (maybe)&lt;/li&gt;
&lt;li&gt;Searchable&lt;/li&gt;
&lt;li&gt;Auto Tag/Auto Categorize&lt;/li&gt;
&lt;li&gt;Can create entries through UI&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MHonArc&lt;/li&gt;
&lt;li&gt;WordPress + WebMail posting&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Let&#039;s Encrypt</title>
<link href="https://www.0ink.net/posts/2015/2015-12-04-lets-encrypt.html"></link>
<id>urn:uuid:d55115a7-d987-2f5a-bcd7-b65b631ab2c5</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This is a service that let's you get SSL certificates for HTTPS. These certificates are trusted by major browsers. See Let's Encrypt This is a barebones howto to get SSL certificates:
git clone https://github.com/letsencrypt/letsencrypt
cd letsencrypt
This contains the client software for let's encrypt.
./letsencrypt-auto certonly --manual
This will start by updating and getting any needed dependencies and then jump to a wizard like configuration to get this done. Follow the prompts and pay special attention on the prompt used to validate your domain. (You need to create a couple of folders and a file with the right content). Afterwards your certificates will be in:
...]]></summary>
<content type="html">&lt;p&gt;This is a service that let&#039;s you get SSL certificates for HTTPS. These certificates are trusted by major browsers. See &lt;a href=&quot;https://letsencrypt.org/about/&quot;&gt;Let&#039;s Encrypt&lt;/a&gt; This is a barebones &lt;em&gt;howto&lt;/em&gt; to get SSL certificates:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone https://github.com/letsencrypt/letsencrypt
cd letsencrypt&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This contains the client software for let&#039;s encrypt.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./letsencrypt-auto certonly --manual&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will start by updating and getting any needed dependencies and then jump to a &lt;em&gt;wizard&lt;/em&gt; like configuration to get this done. Follow the prompts and pay special attention on the prompt used to validate your domain. (You need to create a couple of folders and a file with the right content). Afterwards your certificates will be in:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/etc/letsencrypt/live/mydomain.tld&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then go to your CPanel configuration, then upload:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;privkey.pem&lt;/code&gt; to &lt;strong&gt;Private Keys&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cert.pem&lt;/code&gt; to &lt;strong&gt;Certificates&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then you go to &lt;strong&gt;Manage SSL Hosts -&amp;gt; Browse Certificates&lt;/strong&gt;, pick the right certificate. Then paste &lt;code&gt;chain.pem&lt;/code&gt; (from /etc/letsencrypt/live/mydomain.tld) to the CA Bundle box.&lt;/p&gt;</content>
</entry>
<entry>
<title>undup</title>
<link href="https://www.0ink.net/posts/2015/2015-11-30-undup.html"></link>
<id>urn:uuid:68c3298f-b686-bb03-95b0-d39fd0965547</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[So, after a long while, I wrote a new C language program. As usual,
the same things that I dislike about C programming popped up,
specifically the need for low level data structures and manual
memory management.
I did learn some new things:

...]]></summary>
<content type="html">&lt;p&gt;So, after a long while, I wrote a new C language program. As usual,
the same things that I dislike about C programming popped up,
specifically the need for low level data structures and manual
memory management.&lt;/p&gt;
&lt;p&gt;I did learn some new things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/troydhanson/uthash/&quot;&gt;uthash&lt;/a&gt; : I have used this library before, but there were a few new features that I did not know before, specifically it not only includes the hash library, but also some other &lt;em&gt;high level&lt;/em&gt; structures that were quite handy.&lt;/li&gt;
&lt;li&gt;Unit testing : So I started using &lt;a href=&quot;https://github.com/danfis/cu/&quot;&gt;cu&lt;/a&gt;, a C unit testing library. Frst time I write a program with integrated unit-testing. I can see its usefulness, but it does feel like a lot of work. For a casual programmer like myself, does feel like an over-kill.&lt;/li&gt;
&lt;li&gt;Continuous integration with &lt;a href=&quot;http://travis-ci.org/alejandroliu/undup&quot;&gt;Travis-CI&lt;/a&gt; : For this project I tried using a CI tool. I chose &lt;a href=&quot;http://travis-ci.org/&quot;&gt;Travis-CI&lt;/a&gt; because it integrates with &lt;a href=&quot;http://github.com/&quot;&gt;GitHub&lt;/a&gt;. This only makes sense with unit testing. Once again, for a casual programmer like myself, it feels like a bit too much, but I can see how it would be useful if you have multiple contributors to the same project repository.&lt;/li&gt;
&lt;li&gt;Creating binaries for a Zyxel NSA 325 v2 : So I got the NSA 325v2 SDK, and I am cross compiling for it. Quite straightforward, but still, something new.&lt;/li&gt;
&lt;li&gt;An interesting feature of this code, is that, when possible, it is object oriented.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Anyway, this project can be found in github:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alejandroliu/undup&quot;&gt;Undup github repository&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Markdown Javascript editors</title>
<link href="https://www.0ink.net/posts/2015/2015-11-11-md-js-editors.html"></link>
<id>urn:uuid:0fc8bbb7-761c-8ded-a624-976cb565c313</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[VUE JS: Includes a Markdown editor example that allows edit with online preview next to it
Embeddable JS Markdown editor : Has a button to
preview
Editors that edit in preview-like mode

editor
...]]></summary>
<content type="html">&lt;p&gt;&lt;a href=&quot;http://vuejs.org/&quot;&gt;VUE JS&lt;/a&gt;: Includes a Markdown editor example that allows edit with online preview next to it&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://epiceditor.com/&quot;&gt;Embeddable JS Markdown editor&lt;/a&gt; : Has a button to
preview&lt;/p&gt;
&lt;p&gt;Editors that edit in preview-like mode&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/lepture/editor&quot;&gt;editor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/NextStepWebs/simplemde-markdown-editor/&quot;&gt;simplemde&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jbt/markdown-editor&quot;&gt;markdown&lt;/a&gt; (With GFM)&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Picade Todo</title>
<link href="https://www.0ink.net/posts/2015/2015-10-11-picade-todo.html"></link>
<id>urn:uuid:72deb563-5ecf-2d13-a5a0-2277e506c8bc</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[
key mappings

look up and label default mappings


...]]></summary>
<content type="html">&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;http://forums.pimoroni.com/t/picade-pcb-emulator-key-mapping/922&quot;&gt;key mappings&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;look up and label default mappings&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;  { KEY_UP_ARROW,    UP     },
  { KEY_DOWN_ARROW,  DOWN   },
  { KEY_LEFT_ARROW,  LEFT   },
  { KEY_RIGHT_ARROW, RIGHT  },

  { KEY_LEFT_CTRL,   BTN_1  },
  { KEY_LEFT_ALT,    BTN_2  },
  { &#039; &#039;,             BTN_3  },
  { KEY_LEFT_SHIFT,  BTN_4  },
  { &#039;z&#039;,             BTN_5  },
  { &#039;x&#039;,             BTN_6  },

  { &#039;s&#039;,             START  },
  { &#039;c&#039;,             COIN   },
  { KEY_RETURN,      ENTER  },
  { KEY_ESC,         ESCAPE },

  /* Change these lines to set key bindings for VOL_UP and VOL_DN */
   { &#039;u&#039;,      VOL_UP  },
   { &#039;d&#039;,      VOL_DN },&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Properly secure pi to case&lt;/li&gt;
&lt;li&gt;Properly configure MAME&lt;/li&gt;
&lt;li&gt;SSH to picade&lt;/li&gt;
&lt;li&gt;Add roms to picade&lt;/li&gt;
&lt;li&gt;Add a external port to plugin controllers&lt;/li&gt;
&lt;li&gt;scrapping games How?&lt;/li&gt;
&lt;li&gt;netplay&lt;/li&gt;
&lt;li&gt;setup battery power&lt;/li&gt;
&lt;li&gt;Change the art work&lt;/li&gt;
&lt;li&gt;Properly install power button&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://picraftbukkit.webs.com/pi-minecraft-server-how-to&quot;&gt;minecraft server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Install Java and how to run minecraft on PC&lt;/li&gt;
&lt;li&gt;Add a HD for PS1 games &lt;a href=&quot;https://shop.pimoroni.com/products/sata-hard-drive-to-usb-adapter&quot;&gt;sata adapter&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;Wiring...&quot; name=&quot;Wiring...&quot;&gt;Wiring...&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;GPIO --- 220Ohm --- +LED- ---&amp;gt; GND&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Python&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BCM)
GPIO.setup(25, GPIO.OUT)
GPIO.output(25, 1)
GPIO.cleanup()

GPIO.setup(22, GPIO.IN)
GPIO.input(22) == bool&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ASCIART&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;                            +--- 10 kOhm --- GND
                            |
                            |
                            |      _-v
GPIO -- 1 kOhm --+---+    +------ 3.3V&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://www.arduino.cc/en/Tutorial/Button&quot;&gt;button&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GND -- 10K Ohm --+---+ SW +--- 5V
                 |
GPIO----------------+&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://www.arduino.cc/en/Tutorial/JoyStick&quot;&gt;joystic&lt;/a&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>Centos7/RHEL7 FirewallD -- the least you need to know</title>
<link href="https://www.0ink.net/posts/2015/2015-09-21-centos7rhel7-firewalld-the-least-you-need-to-know.html"></link>
<id>urn:uuid:2f2ae9d7-7322-2985-8669-df16986d5e9a</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This post is just a simple hints-tips to get something going with FirewallD without going into too much detail.

Checking if you are using firewalld:

firewall-cmd --state

...]]></summary>
<content type="html">&lt;p&gt;This post is just a simple hints-tips to get something going with FirewallD without going into too much detail.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Checking if you are using &lt;strong&gt;firewalld&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;firewall-cmd --state&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Check your zones (needed later when opening ports):
&lt;ul&gt;
&lt;li&gt;firewall-cmd --get-default-zone&lt;/li&gt;
&lt;li&gt;firewall-cmd --get-active-zones&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Checking what is active:
&lt;ul&gt;
&lt;li&gt;firewall-cmd --zone=public --list-all&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Opening services:
&lt;ul&gt;
&lt;li&gt;firewall-cmd --zone=public --add-service=http Or alternatively:&lt;/li&gt;
&lt;li&gt;firewall-cmd --permanent --zone=public --add-service=http&lt;/li&gt;
&lt;li&gt;firewall-cmd --reload Services are defined in /usr/lib/firewalld/services and /etc/firewalld/services.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Opening ports:
&lt;ul&gt;
&lt;li&gt;firewall-cmd --permanent --zone=public --add-port=443/tcp&lt;/li&gt;
&lt;li&gt;firewall-cmd --reload&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ol&gt;</content>
</entry>
<entry>
<title>Raspberry Pi Thin Client</title>
<link href="https://www.0ink.net/posts/2015/2015-08-17-raspberry-pi-thin-client.html"></link>
<id>urn:uuid:16f1fce2-3a88-883e-2985-7511202dc265</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Thin Client project want to create a very low price thin client over
Raspberry Pi board! Microsoft RDC, Citrix ICA, VMWare View, OpenNX &amp;
SPICE
RPITC
...]]></summary>
<content type="html">&lt;p&gt;Thin Client project want to create a very low price thin client over
Raspberry Pi board! Microsoft RDC, Citrix ICA, VMWare View, OpenNX &amp;amp;
SPICE&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://rpitc.blogspot.nl/&quot;&gt;RPITC&lt;/a&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>Raspberry pi notes</title>
<link href="https://www.0ink.net/posts/2015/2015-08-11-rasp-pi-notes.html"></link>
<id>urn:uuid:29999dfc-abbe-f629-d1dd-b0c9232c63bf</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[raspberry pi shops

NL based

sos solutions
hackerstore
...]]></summary>
<content type="html">&lt;h2 id=&quot;raspberry+pi+shops&quot; name=&quot;raspberry+pi+shops&quot;&gt;raspberry pi shops&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;NL based
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.sossolutions.nl&quot;&gt;sos solutions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.hackerstore.nl/&quot;&gt;hackerstore&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.antratek.nl/&quot;&gt;antratek&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.kiwi-electronics.nl/&quot;&gt;kiwi-electonics&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;UK based
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://thepihut.com/&quot;&gt;the pi hut&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.modmypi.com/&quot;&gt;modmypi&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://shop.pimoroni.com/&quot;&gt;pimorni&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;International
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://mouser.com&quot;&gt;mouser&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://conrad.nl&quot;&gt;conrad&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Hardware to attach/secure Raspberry Pi boards&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;4 M2 16mm bolts, + nuts, + spacers?
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.conrad.nl/nl/zeskantmoeren-m25-din-934-kunststof-10-stuks-toolcraft-830405-830405.html&quot;&gt;moeren&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.conrad.nl/nl/toolcraft-zeskantbouten-m25-16-mm-buitenzeskant-inbus-din-933-kunststof-10-stuks-830220.html&quot;&gt;bouten&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.conrad.nl/nl/modelcraft-bec-verlengkabel-208429.html&quot;&gt;verleng kabel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.conrad.nl/nl/usb-20-verlengkabel-1x-usb-20-stekker-intern-8-polig-1x-usb-20-bus-intern-8-polig-030-m-grijs-vergulde-steekcontacten-ul-gecertificeerd-971778.html&quot;&gt;USB Connectors&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Creating a Read-Only root for Raspbian:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://hallard.me/raspberry-pi-read-only/&quot;&gt;read only&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://blog.pi3g.com/2014/04/make-raspbian-system-read-only/&quot;&gt;ro raspbian&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://blog.gegg.us/2014/03/a-raspbian-read-only-root-fs-howto/&quot;&gt;ro rootfs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Hack/cycle something: &lt;a href=&quot;https://learn.adafruit.com/raspberry-gear/introduction&quot;&gt;rapsberry gear&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;3D+Printing+Cases%3A&quot; name=&quot;3D+Printing+Cases%3A&quot;&gt;3D Printing Cases:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://raspberrypi.stackexchange.com/questions/9934/is-there-an-accurate-3d-cad-model-of-the-version-b-board&quot;&gt;b-board mode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://i.materialise.com/blog/how-to-design-a-raspberry-pi-case-for-3d-printing&quot;&gt;howto&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.3dhubs.com/&quot;&gt;3dhubs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Replacing Emacs with Atom</title>
<link href="https://www.0ink.net/posts/2015/2015-07-24-replacing-emacs.html"></link>
<id>urn:uuid:8e0f1c45-0037-a851-b688-462125ecaf1c</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[
As an old UNIX guy I have been using
emacs for years.
So in a way, I am very comfortable with using it and most of keyboard
shortcuts. But, it really is an old animal and I have been thinking
that I should be moving to a more modern replacement to it for quite
...]]></summary>
<content type="html">&lt;p&gt;&lt;img src=&quot;/images/2015/atom.png&quot; alt=&quot;atom&quot; /&gt;&lt;/p&gt;
&lt;p&gt;As an old UNIX guy I have been using
&lt;a href=&quot;https://www.gnu.org/software/emacs/emacs.html&quot;&gt;emacs&lt;/a&gt; for years.
So in a way, I am very comfortable with using it and most of keyboard
shortcuts. But, it really is an old animal and I have been thinking
that I should be moving to a more modern replacement to it for quite
a while.&lt;/p&gt;
&lt;p&gt;My latest attempt (and the most serious attempt to date) has been
trying &lt;a href=&quot;http://atom.io/&quot;&gt;atom&lt;/a&gt;. &lt;a href=&quot;http://atom.io/&quot;&gt;Atom&lt;/a&gt; is a new
editor from the makers of &lt;a href=&quot;https://github.com/&quot;&gt;github&lt;/a&gt; which claims
to have been inspired by &lt;a href=&quot;https://www.gnu.org/software/emacs/emacs.html&quot;&gt;emacs&lt;/a&gt;
and also supports the latest web technologies.&lt;/p&gt;
&lt;p&gt;After using it for some time, I found the following conclusions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;I am really used to using CTRL + key to move around. And switching to the dedicated arrow and home/end keys feels like a step backwards. I might be old fashioned, but it really is about keeping your fingers in the keyboard &amp;quot;home&amp;quot; row. Also, while the arrow keys are easy to find, I have trouble with the home/end keys (which apparently I use a lot when programming). Specially because I switch between a laptop and full size keyboard all the time.&lt;/li&gt;
&lt;li&gt;I really like the automatic programming style automation in Emacs.&lt;/li&gt;
&lt;li&gt;I miss the record macro/execute macro facility of Emacs.&lt;/li&gt;
&lt;li&gt;The automatic &amp;quot;(&amp;quot; inserts &amp;quot;)&amp;quot; really annoys me.&lt;/li&gt;
&lt;li&gt;Browsing for extensions in &amp;quot;Atom&amp;quot; seems a bit non-intuitive to me.&lt;/li&gt;
&lt;li&gt;I am used to using the command line and open the files in a running editor directly from there. I am able to configure emacs to do this, it is not clear to me how to do this with Atom yet.&lt;/li&gt;
&lt;li&gt;I don&#039;t know why I am so used to the &lt;a href=&quot;https://www.gnu.org/software/emacs/emacs.html&quot;&gt;Emacs&lt;/a&gt; CTRL+S (search) functionality.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The following things I really like:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Syntax highlighting is quite solid&lt;/li&gt;
&lt;li&gt;The project view pane is very useful.&lt;/li&gt;
&lt;li&gt;The Markdown preview pane.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So up to know, looks promising but I am not convinced. I still using &lt;a href=&quot;https://www.gnu.org/software/emacs/emacs.html&quot;&gt;emacs&lt;/a&gt; specially because it sometimes feel that &lt;a href=&quot;http://atom.io/&quot;&gt;atom&lt;/a&gt; is slow to start.&lt;/p&gt;</content>
</entry>
<entry>
<title>Online IDEs</title>
<link href="https://www.0ink.net/posts/2015/2015-07-24-online-ides.html"></link>
<id>urn:uuid:3f5b7394-7bfc-4a1a-ad85-39753085aed8</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[If you want to move to the cloud and like to code like me, this is
kinda of a basic necessity.
This applies in particular to Chromebook users.
5 Best online IDEs
...]]></summary>
<content type="html">&lt;p&gt;If you want to move to the cloud and like to code like me, this is
kinda of a basic necessity.&lt;/p&gt;
&lt;p&gt;This applies in particular to Chromebook users.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.chromebookhq.com/five-best-online-ides-making-the-switch-to-a-chromebook/&quot;&gt;5 Best online IDEs&lt;/a&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>Lifehacker App Guides</title>
<link href="https://www.0ink.net/posts/2015/2015-07-24-lifehacker-app-guides.html"></link>
<id>urn:uuid:00c9964c-77c3-091b-a1f1-4e9eb1e4dc43</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[These two hyperlinks from Lifehacker are quite useful:

iPhone App Guide
Android App Guide

...]]></summary>
<content type="html">&lt;p&gt;These two hyperlinks from Lifehacker are quite useful:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://lifehacker.com/5825402/the-lifehacker-app-directory-iphone&quot; title=&quot;Lifehacker App directory for iPhone&quot;&gt;iPhone App Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://lifehacker.com/5825401/the-lifehacker-app-directory-android&quot; title=&quot;Lifehacker App directory for Android&quot;&gt;Android App Guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Upload to OpenWRT</title>
<link href="https://www.0ink.net/posts/2015/2015-07-11-openwrt-upd.html"></link>
<id>urn:uuid:471f09b8-f4b8-9870-b4df-231b98c8fa30</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Base 64 decoding: coreutils-base64
#!/usr/local/bin/haserl --upload-limit=4096 --upload-dir=/tmp 
content-type: text/html

&lt;html&gt;&lt;body&gt;
&lt;form action="&lt;% echo -n $SCRIPT_NAME %&gt;" method=POST enctype="multipart/form-data" &gt;
...]]></summary>
<content type="html">&lt;p&gt;Base 64 decoding: &lt;code&gt;coreutils-base64&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/local/bin/haserl --upload-limit=4096 --upload-dir=/tmp 
content-type: text/html

&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;
&amp;lt;form action=&quot;&amp;lt;% echo -n $SCRIPT_NAME %&amp;gt;&quot; method=POST enctype=&quot;multipart/form-data&quot; &amp;gt;
&amp;lt;input type=file name=uploadfile&amp;gt;
&amp;lt;input type=submit value=GO&amp;gt;
&amp;lt;br&amp;gt;
&amp;lt;% if test -n &quot;$HASERL_uploadfile_path&quot;; then %&amp;gt;
        &amp;lt;p&amp;gt;
        You uploaded a file named &amp;lt;b&amp;gt;&amp;lt;% echo -n $FORM_uploadfile_name %&amp;gt;&amp;lt;/b&amp;gt;, and it was
        temporarily stored on the server as &amp;lt;i&amp;gt;&amp;lt;% echo $HASERL_uploadfile_path %&amp;gt;&amp;lt;/i&amp;gt;.  The
        file was &amp;lt;% cat $HASERL_uploadfile_path | wc -c %&amp;gt; bytes long.&amp;lt;/p&amp;gt;
        &amp;lt;% rm -f $HASERL_uploadfile_path %&amp;gt;&amp;lt;p&amp;gt;Don&#039;t worry, the file has just been deleted
        from the web server.&amp;lt;/p&amp;gt;
&amp;lt;% else %&amp;gt;
        You haven&#039;t uploaded a file yet.
&amp;lt;% fi %&amp;gt;
&amp;lt;/form&amp;gt;
&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;http://haserl.sourceforge.net/manpage.html&quot;&gt;haserl man page&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Uploader tool: &lt;a href=&quot;https://curl.haxx.se/docs/httpscripting.html#File_Upload_POST&quot;&gt;post&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Disable/Relocate cgi-bin&lt;/p&gt;</content>
</entry>
<entry>
<title>organizing notes</title>
<link href="https://www.0ink.net/posts/2015/2015-06-11-org-notes.html"></link>
<id>urn:uuid:b38446b4-93e4-66a0-3441-bbbb91fb832e</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[My Documents
DOCUMENTS

Project Folder

old
...]]></summary>
<content type="html">&lt;h2 id=&quot;My+Documents&quot; name=&quot;My+Documents&quot;&gt;My Documents&lt;/h2&gt;
&lt;p&gt;DOCUMENTS&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Project Folder
&lt;ul&gt;
&lt;li&gt;old&lt;/li&gt;
&lt;li&gt;YYYY&lt;/li&gt;
&lt;li&gt;deliverables&lt;/li&gt;
&lt;li&gt;clips?&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;category folder
&lt;ul&gt;
&lt;li&gt;expenses? expense reports and digital receipts&lt;/li&gt;
&lt;li&gt;regs - passwords, registrations, etc...&lt;/li&gt;
&lt;li&gt;nice notes - thank you letters, etc.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Personal folder
&lt;ul&gt;
&lt;li&gt;info or important
health account data, friends contacts, etc&lt;/li&gt;
&lt;li&gt;clips&lt;/li&gt;
&lt;li&gt;writing - personal writing, notes, letter, drafts,&lt;/li&gt;
&lt;li&gt;taxes &lt;year&gt;
&lt;year&gt; folder&lt;/year&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;logs
&lt;ul&gt;
&lt;li&gt;activity log&lt;/li&gt;
&lt;li&gt;travel log&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Naming convention:&lt;/p&gt;
&lt;initials&gt;-&lt;month&gt;&lt;day&gt;-&lt;type&gt;.&lt;ext&gt;

- initials : author initials or source initials
- monthday : 2 digits each
- type : type of document

# TODO Lists

- Work
  - Outlook based
  - Tickler File
  - Must Do List
  - E-mail to TODO
- Personal
  - Google Tasks

# Weekly Review Steps

NOTE: Clean-up temp folders

* Collect loose paper notes and materials.  (business cards, receipts, etc. - put in in basket for processing)
* Get IN to zero
* Empty your head (write down any new projects, action items, etc.)
* Review Action lists (Mark off completed actions &amp;amp; review for reminders of further action steps to capture)
* Review Previous Calendar Data (review for remaining action items, reference information, etc.)
* Review Upcoming Calendar Data
* Review Waiting For List (Records appropriate actions for any needed follow-up &amp;amp; check off received items)
* Review Project and Larger Outcome lists (ensure that at least one kick-start action is in your system for each)
* Review Any relevant checklists
* Review Someday/Maybe List  (Check for any projects that may have become active and transfer them to &quot;Projects&quot; &amp;amp; delete items no longer of interest)
* Review &quot;Pending&quot; and Support Files  (Browse through all work-in-progress support material to trigger new actions, completions, and waiting-fors)

## Six Level Model for Reviewing Your Own Work

1. current actions
2. current projects
3. areas of responsibility
4. 1-2 year goals
5. 3-5 year vision
6. big picture view

* * *

* projects: clearly defined outcomes and the next
* actions to move them towards closure
* horizontal focus: reminders placed in a trusted system
* that is reviewed regularly
* vertical focus: informal back of the envelope planning

# Task Lists

- @ANYWHERE : Actions that can be done anywhere (rare?)
- @CALLS : Phonecalls
- @ERRANDS : Actions that I can do while going about
- @HOME : Actions that can only be done at home
- @HOME_PC : Actions that can be done at a PC (home PC?) about home.
- @REVIEW : Items for review. Should be only text. Attachments should go to
  my Dropbox folder.
- @WAITING_FOR : Tracker items that need to be follow-up later
- @WORK : Actions that can only be done at the office.
- @WORK_PC : Actions that can be done at a PC (work PC) about work.
- @AGENDAS : Notes on what to discuss with different people
- SOMEDAY_MAYBE : Idea parking lot&lt;/ext&gt;&lt;/type&gt;&lt;/day&gt;&lt;/month&gt;&lt;/initials&gt;</content>
</entry>
<entry>
<title>Another Markdown Editor</title>
<link href="https://www.0ink.net/posts/2015/2015-04-30-another-markdown-editor.html"></link>
<id>urn:uuid:258fb9c9-f8fc-2221-fd0a-268688a20d58</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This one is GitHubFlavored markdown...
markdown editor
...]]></summary>
<content type="html">&lt;p&gt;This one is GitHubFlavored markdown...&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://jbt.github.io/markdown-editor/&quot;&gt;markdown editor&lt;/a&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>Web Links</title>
<link href="https://www.0ink.net/posts/2015/2015-04-19-web-links.html"></link>
<id>urn:uuid:b29ed475-b567-083d-892d-5b8bc527ab1d</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Here a few web-links to interesting web apps.
It covers stuff about password security and checking if web sites
are down, etc etc.

Down For Everyone or Just Me:
If you're getting an error when visiting a certain site, it could be down or something could be wrong on your end. To see which
...]]></summary>
<content type="html">&lt;p&gt;Here a few web-links to interesting web apps.&lt;/p&gt;
&lt;p&gt;It covers stuff about password security and checking if web sites
are down, etc etc.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2015/ifysfxqtv2dyygl0b09k.jpg&quot; alt=&quot;ifysfxqtv2dyygl0b09k&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.downforeveryoneorjustme.com/&quot;&gt;Down For Everyone or Just Me&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;If you&#039;re getting an error when visiting a certain site, it could be down or something could be wrong on your end. To see which
it is, head to and type in the web site&#039;s domain. It&#039;ll let you know if it&#039;s actually down or whether you need to do a little more
troubleshooting. You can head there quicker by typing in .&lt;/p&gt;
&lt;p&gt;If you&#039;re curious how fast your internet is for any reason, this is the
site to check. It&#039;ll give you both and upload and download speed, so you
can find out if you&#039;re getting what you pay for (or if you&#039;re just
getting faster speeds than your friends). Just load it up and
click &amp;quot;Begin Test&amp;quot; to get started.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2015/jcnkq3n1jdkg3mtindvt.jpg&quot; alt=&quot;jcnkq3n1jdkg3mtindvt&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://howsecureismypassword.net/&quot;&gt;How Secure Is My Password?&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;Does what it says on the tin. Type in a password and it&#039;ll tell you how long it would take to crack.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://whatismyip.org/&quot;&gt;What&#039;s My IP&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;Whether you&#039;re setting up a home media server with Subsonic or you just need to SSH into a computer at home, sometimes
you need to know a computer&#039;s IP address from outside of your network, and this site will tell you what it is.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://canyouseeme.org/&quot;&gt;Can You See Me&lt;/a&gt;: If you&#039;re having connection issues with a certain program, like email, IM, or
BitTorrent, it could be because your firewall or ISP is blocking a certain port that program needs. Canyouseeme.org will
let you type in a port and check if it&#039;s open- if it isn&#039;t, then that
could be the source of your trouble. If it&#039;s open, then you know it&#039;s something else.&lt;/p&gt;</content>
</entry>
<entry>
<title>Fiddle Markdown Tool</title>
<link href="https://www.0ink.net/posts/2015/2015-04-18-fiddle-markdown-tool.html"></link>
<id>urn:uuid:ceeb7cf2-ab74-8ed3-85dd-1c67cb077de7</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[For a quick and simple Markdown Preview:

Fiddle
...]]></summary>
<content type="html">&lt;p&gt;For a quick and simple Markdown Preview:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2015/oudpno5sb9dfgfkvpvgw.png&quot; alt=&quot;oudpno5sb9dfgfkvpvgw&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://fiddle.md/&quot;&gt;Fiddle&lt;/a&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>Code Kingdoms</title>
<link href="https://www.0ink.net/posts/2015/2015-04-17-code-kingdoms.html"></link>
<id>urn:uuid:8916bb8f-f295-f2b8-29f9-b4ad28cda9dc</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Code Kingdoms is targeted towards six- to 13-year olds and looks very
much like your everyday puzzle adventure game. Choose an animal, walk
around a kingdom saving animals through puzzles. The difference is
most of the puzzles require kids to use code elements to solve the
puzzles. At first this is through dragging-and-dropping code snippets,
but as they progress, kids will be typing in code themselves.
...]]></summary>
<content type="html">&lt;p&gt;Code Kingdoms is targeted towards six- to 13-year olds and looks very
much like your everyday puzzle adventure game. Choose an animal, walk
around a kingdom saving animals through puzzles. The difference is
most of the puzzles require kids to use code elements to solve the
puzzles. At first this is through dragging-and-dropping code snippets,
but as they progress, kids will be typing in code themselves.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2015/pugw1qoceliykwmnprbt.png&quot; alt=&quot;pugw1qoceliykwmnprbt&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Besides teaching actual JavaScript through play, Code Kingdoms
also helps kids develop problem-solving skills and the encouragement
to keep pushing on when they&#039;re faced with a challenge in the
game-much like programmers often have to push through challenging
walls.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://codekingdoms.com/&quot;&gt;Code Kingdoms&lt;/a&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>Kerberos Client</title>
<link href="https://www.0ink.net/posts/2015/2015-02-06-kerberos-client.html"></link>
<id>urn:uuid:e0e789e0-3b60-da9b-d283-a24178312c12</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This simple mini how-to goes over the configuration of a
linux system so it can use a Kerberos Realm server
for authentication.


Make sure you have the pam_krb5 rpm files installed. You can check this by running the rpm -qa | grep pam command and seeing whether the pam_krb5 rpm files are listed. If they aren't, you can typically download them in an update of the Linux or Unix operating system that you are running.
...]]></summary>
<content type="html">&lt;p&gt;This simple mini how-to goes over the configuration of a
linux system so it can use a Kerberos Realm server
for authentication.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Make sure you have the pam_krb5 rpm files installed. You can check this by running the &lt;code&gt;rpm -qa | grep pam&lt;/code&gt; command and seeing whether the pam_krb5 rpm files are listed. If they aren&#039;t, you can typically download them in an update of the Linux or Unix operating system that you are running.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the line to the &amp;quot;/etc/pam.d/system-auth&amp;quot; part of the auth section of Kerberos. Add it after the &amp;quot;pam_unix.so&amp;quot; line:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;auth sufficient /lib/security/pam_krb5.so use_first_pass forwardable&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the line to the &amp;quot;/etc/pam.d/system-auth&amp;quot; part of the password section of Kerberos. Add it after the &amp;quot;pam_unix.so&amp;quot; line:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;password sufficient /lib/security/pam_krb5.so use_authtok&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the line to the &amp;quot;/etc/pam.d/system-auth&amp;quot; part of the session section of Kerberos. Add it after the &amp;quot;pam_unix.so&amp;quot; line:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;session optional /lib/security/pam_krb5.so&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;</content>
</entry>
<entry>
<title>HP Envy 4504 Set-up</title>
<link href="https://www.0ink.net/posts/2015/2015-02-06-hp-envy-4504-set-up.html"></link>
<id>urn:uuid:0507c985-d8d8-dcea-52f6-d3194a9ebce0</id>
<updated>2022-11-11T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[I bought a HP Envy 4504.  Overall I am happy with it.  This is how
I configure it so I can use with Linux.
This mini howto applies to ArchLinux, void linux and Centos/RedHat distributions.
Installation
Archlinux:
cups, hplip, python2, sane
...]]></summary>
<content type="html">&lt;p&gt;I bought a HP Envy 4504.  Overall I am happy with it.  This is how
I configure it so I can use with Linux.&lt;/p&gt;
&lt;p&gt;This mini howto applies to ArchLinux, void linux and Centos/RedHat distributions.&lt;/p&gt;
&lt;h3 id=&quot;Installation&quot; name=&quot;Installation&quot;&gt;Installation&lt;/h3&gt;
&lt;p&gt;Archlinux:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cups, hplip, python2, sane&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Centos:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cups, hplip, hplip-gui, sane&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Some optional dependancies may be needed.&lt;/p&gt;
&lt;p&gt;void linux:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hplip-gui&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And for scanning, install:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;simple-scan and/or xsane&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Configuration+Arch+Linux+and+Centos%2FRedHat&quot; name=&quot;Configuration+Arch+Linux+and+Centos%2FRedHat&quot;&gt;Configuration Arch Linux and Centos/RedHat&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl enable cups
sudo systemctl start cups
sudo hp-setup&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;hp-setup -i Select &amp;quot;Network&amp;quot;, and &amp;quot;Advanced Options -&amp;gt; Manual Discovery&amp;quot; Printer: npr1 PPD File:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/usr/share/ppd/HP/hp-envy_4500_series.ppd&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Uncoment &lt;code&gt;hpaio&lt;/code&gt; from &lt;code&gt;/etc/sane/dll.conf&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;void+linux+configuration&quot; name=&quot;void+linux+configuration&quot;&gt;void linux configuration&lt;/h3&gt;
&lt;p&gt;These are void linux specific settings:&lt;/p&gt;
&lt;p&gt;enable cups:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ln -s /etc/sv/cupsd /var/service&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add printer (run with &lt;code&gt;sudo&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;print_host=npr1
hp-setup $print_host&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Tweaks&quot; name=&quot;Tweaks&quot;&gt;Tweaks&lt;/h3&gt;
&lt;p&gt;Some commands:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;lpstat -p
cupsenable printer&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also since it is a WIFI printer no  To prevent thisrmally it will go into sleep/power
save mode. This means if you then try to print from cups it will fail
(printer is asleep). Subsequent prints should work but now cupsd has
flagged the printer as paused.  To prevent this you should run this
command as root:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;lpadmin -p ENVY_4500 -o printer-error-policy=retry-job&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;More configuration commands:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Set default paper size:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;echo a4 &amp;gt; /etc/papersize&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;Updates&quot; name=&quot;Updates&quot;&gt;Updates&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;2022-11-06: Removed from voidlinux:
&lt;ul&gt;
&lt;li&gt;uncompress PPD file (otherwise it is not recognized)
so that it runs:&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;removed OBSOLEtE patch&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;For scanning, uncoment &lt;code&gt;hpaio&lt;/code&gt; from &lt;code&gt;/etc/sane/dll.conf&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;2020-03-09 : Removed:
&lt;ul&gt;
&lt;li&gt;To prevent this you should configure
the default &lt;code&gt;ErrorPolicy&lt;/code&gt; in &lt;code&gt;/etc/cups/cupsd.conf&lt;/code&gt; by adding in the top
scope: &amp;quot;ErrorPolicy retry-job&amp;quot;&lt;/li&gt;
&lt;li&gt;References: &lt;a href=&quot;https://superuser.com/questions/280396/how-to-resume-cups-printer-from-command-line&quot;&gt;superuser.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;2019-02-19 : Added &lt;a href=&quot;http://voidlinux.org&quot;&gt;void linux&lt;/a&gt; instructions.&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>RPMGOT</title>
<link href="https://www.0ink.net/posts/2015/2015-02-04-rpmgot.html"></link>
<id>urn:uuid:5a637554-5e2f-cdc0-0a63-d45fa77d16d2</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Software package download proxy
rpmgot is a simple/lightweight software package download proxy. It was designed to run on an OpenWRT router with some USB storage. So it is fully implemented as an ash script.
The basic idea has been implemented multiple times. For example refer to this article on a squid based implementation.
Unlike squid, which once you include all its dependencies can use up over 1MB of space just to install it, this software has very few dependencies.
The idea is for small developers running the same operating system version(s) would benefit from a local mirror of them, but they don't have so many systems that it's actually reasonable for them to run a full mirror, which would entail rsyncing a bunch of content daily, much of which may be packages would never be used.
rpmgot implements a lazy mirror something that would appear to its client systems as a full mirror, but would act more as a proxy. When a client installed a particular version of a particular package for the first time, it would go fetch them from a &quot;real&quot; mirror, and then cache it for a long time. Subsequent requests for the same package from the &quot;mirror&quot; would be served from cache.
...]]></summary>
<content type="html">&lt;p&gt;&lt;a href=&quot;http://github.com/alejandroliu/rpmgot&quot;&gt;Software package download proxy&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;rpmgot&lt;/code&gt; is a simple/lightweight software package download proxy. It was designed to run on an OpenWRT router with some USB storage. So it is fully implemented as an &lt;code&gt;ash&lt;/code&gt; script.&lt;/p&gt;
&lt;p&gt;The basic idea has been implemented multiple times. For example refer to this &lt;a href=&quot;http://ma.ttwagner.com/lazy-distro-mirrors-with-squid/&quot;&gt;article&lt;/a&gt; on a &lt;a href=&quot;http://www.squid-cache.org/&quot;&gt;squid&lt;/a&gt; based implementation.&lt;/p&gt;
&lt;p&gt;Unlike squid, which once you include all its dependencies can use up over 1MB of space just to install it, this software has very few dependencies.&lt;/p&gt;
&lt;p&gt;The idea is for small developers running the same operating system version(s) would benefit from a local mirror of them, but they don&#039;t have so many systems that it&#039;s actually reasonable for them to run a full mirror, which would entail rsyncing a bunch of content daily, much of which may be packages would never be used.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;rpmgot&lt;/code&gt; implements a &lt;em&gt;lazy&lt;/em&gt; mirror something that would appear to its client systems as a full mirror, but would act more as a proxy. When a client installed a particular version of a particular package for the first time, it would go fetch them from a &amp;quot;real&amp;quot; mirror, and then cache it for a long time. Subsequent requests for the same package from the &amp;quot;mirror&amp;quot; would be served from cache.&lt;/p&gt;
&lt;p&gt;The RPM files are cached for a very long time. Normally it is an awful, awful idea for proxy servers to do interfere with the &lt;code&gt;Cache-Control / Expires&lt;/code&gt; headers that sites serve. But in the case of a mirror, we know that any updates to a package will necessarily bump the version number in the URL. Ergo, we can pretty safely cache RPMs indefinitely.&lt;/p&gt;
&lt;p&gt;You can find this in &lt;a href=&quot;http://github.com/alejandroliu/rpmgot&quot;&gt;Github&lt;/a&gt;.&lt;/p&gt;</content>
</entry>
<entry>
<title>SSL Certificates</title>
<link href="https://www.0ink.net/posts/2015/2015-01-06-ssl-certificates.html"></link>
<id>urn:uuid:0df847bd-a020-8728-bee2-a5da51f9e6f3</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[So it is is a more dangerous world out there. You can start securing web sites using self signed certificates. Another option is to:

Use CloudFlare. This will use a CF certificate from the CF CDN to the web site, while using a self-signed certificate between the CF CDN to your web server.
Use startssl

...]]></summary>
<content type="html">&lt;p&gt;So it is is a more dangerous world out there. You can start securing web sites using self signed certificates. Another option is to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Use CloudFlare. This will use a CF certificate from the CF CDN to the web site, while using a self-signed certificate between the CF CDN to your web server.&lt;/li&gt;
&lt;li&gt;Use &lt;a href=&quot;https://www.startssl.com/&quot;&gt;startssl&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</content>
</entry>
<entry>
<title>Convert HTML to Markdown</title>
<link href="https://www.0ink.net/posts/2015/2015-01-06-mardownifier.html"></link>
<id>urn:uuid:bf2d755e-cb57-9ea1-c658-a2762ff4a718</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[These web sites convert to Markdown:

Mardownifier: Convert the given URL
to-markdown: Convert HTML snippets
turndown

...]]></summary>
<content type="html">&lt;p&gt;These web sites convert to Markdown:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://heckyesmarkdown.com/&quot;&gt;Mardownifier&lt;/a&gt;: Convert the given URL&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://domchristie.github.io/to-markdown/&quot;&gt;to-markdown&lt;/a&gt;: Convert HTML snippets&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://domchristie.github.io/turndown/&quot;&gt;turndown&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Raspberry Pi - Low cost CCTV</title>
<link href="https://www.0ink.net/posts/2014/2014-09-13-raspberry-pi-low-cost-cctv.html"></link>
<id>urn:uuid:47ebb255-3510-6aea-4534-77a83e17c44a</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[A good tutorial on creating a low cost surveillance camera using
the raspberry Pi camera module and one of thos fake surveillance
camera things.

Instructables has a good tutorial on creating a low cost surveillance camera.
Essentially makes use of a Pi, the Camera module and fitted into one of those inexpensive fake surveillance cameras.
...]]></summary>
<content type="html">&lt;p&gt;A good tutorial on creating a low cost surveillance camera using
the raspberry Pi camera module and one of thos fake surveillance
camera things.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2014/FJJOOSJHO7X6PIT.MEDIUM.jpg&quot; alt=&quot;FJJOOSJHO7X6PIT.MEDIUM&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.instructables.com/id/Raspberry-Pi-as-low-cost-HD-surveillance-camera/&quot;&gt;Instructables&lt;/a&gt; has a good tutorial on creating a low cost surveillance camera.&lt;/p&gt;
&lt;p&gt;Essentially makes use of a Pi, the Camera module and fitted into one of those inexpensive fake surveillance cameras.&lt;/p&gt;
&lt;p&gt;It uses &lt;a href=&quot;http://www.lavrsen.dk/foswiki/bin/view/Motion&quot;&gt;motion&lt;/a&gt; for the motion detection software.&lt;/p&gt;</content>
</entry>
<entry>
<title>Raspberry Pi as a Stratum-1 NTP Server</title>
<link href="https://www.0ink.net/posts/2014/2014-09-13-raspberry-pi-as-a-stratum-1-ntp-server.html"></link>
<id>urn:uuid:92b10c83-d540-7b62-cc30-8b1381bbfee9</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This is something I found:

http://www.satsignal.eu/ntp/Raspberry-Pi-NTP.html

Essentially it requires pairing a Raspberry Pi with a
NTPI Raspberry Pi GPS addon board
...]]></summary>
<content type="html">&lt;p&gt;This is something I found:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.satsignal.eu/ntp/Raspberry-Pi-NTP.html&quot;&gt;http://www.satsignal.eu/ntp/Raspberry-Pi-NTP.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Essentially it requires pairing a Raspberry Pi with a
&lt;a href=&quot;http://ava.upuaut.net/store/index.php?route=product/product&amp;amp;path=59_60&amp;amp;product_id=95&quot;&gt;NTPI Raspberry Pi GPS addon board&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;On the software side of things you need
&lt;a href=&quot;http://vanheusden.com/time/rpi_gpio_ntp/&quot;&gt;rpi_gpio_ntp&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2014/Pi-GPS-shield-2013-10-15-1533-44-b.jpg&quot; alt=&quot;pi-gps-shield-2013-10-15-1533-44-b&quot; /&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>Incredible PBX for RasPBX</title>
<link href="https://www.0ink.net/posts/2014/2014-09-13-incredible-pbx-for-raspbx.html"></link>
<id>urn:uuid:17e6879b-9bfc-de24-5f8f-49d9ca8f6538</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This is a link to IncrediblePBX for RasPBX. Looks like bundles to run Asterisk PBX'es on a Raspberry Pi. Neat.
...]]></summary>
<content type="html">&lt;p&gt;This is a link to &lt;a href=&quot;http://nerdvittles.com/?p=8222&quot;&gt;IncrediblePBX for RasPBX&lt;/a&gt;. Looks like bundles to run Asterisk PBX&#039;es on a Raspberry Pi. Neat.&lt;/p&gt;</content>
</entry>
<entry>
<title>dev notes 2014</title>
<link href="https://www.0ink.net/posts/2014/2014-06-26-dev-notes.html"></link>
<id>urn:uuid:a93cb988-f676-5928-756a-bffb04eba255</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Replacment for Make and Autoconf:
MakeMe
(If you don't have root but have Android 4+ you can use the
command-line program adb from the Android SDK platform tools to make
backups via a desktop computer)
chromebook ides
...]]></summary>
<content type="html">&lt;p&gt;Replacment for Make and Autoconf:
&lt;a href=&quot;https://embedthis.com/makeme/&quot;&gt;MakeMe&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;(If you don&#039;t have root but have Android 4+ you can use the
command-line program adb from the Android SDK platform tools to make
backups via a desktop computer)&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.chromebookhq.com/five-best-online-ides-making-the-switch-to-a-chromebook/&quot;&gt;chromebook ides&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;Dev+Tools&quot; name=&quot;Dev+Tools&quot;&gt;Dev Tools&lt;/h2&gt;
&lt;p&gt;Alternative languages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;D : better than C, but not over-the-top like C++?  Covers only Win and Linux&lt;/li&gt;
&lt;li&gt;Vala : Kinda like C# but for Gnome.  Covers Win and Linux.  (Android maybe through NDK).&lt;/li&gt;
&lt;li&gt;Java: Kinda over the top and heavy.  Covers Win and Linux.  Android yes, but different GUI library.  iOS probably yes.&lt;/li&gt;
&lt;li&gt;Python: scripting language.  Win, Linux.  Android maybe... iOS maybe...&lt;/li&gt;
&lt;li&gt;Javascript: scripting language. ALL PLATFORMS.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Other options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Python with &lt;a href=&quot;http://kivy.org/&quot;&gt;Kivy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://haxe.org/&quot;&gt;Haxe&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Build+Tools&quot; name=&quot;Build+Tools&quot;&gt;Build Tools&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;MakeKit - autotools look &amp;amp; feel but lighter&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.dervishd.net/libre-software-projects&quot;&gt;mobs&lt;/a&gt;: autoconf workalike.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Resources&quot; name=&quot;Resources&quot;&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.dervishd.net/libre-software-projects&quot;&gt;libre projects&lt;/a&gt; :
syslogd in perl, mobom perl modules.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;My+own+Notes+App&quot; name=&quot;My+own+Notes+App&quot;&gt;My own Notes App&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;JumpNote      +      OI Notpad
(Background          (Tags support)
Sync)
          V
     Simple Note backend
          V
        Tags UI
     (Filter, modify tags)
          V
        Task UI
          V
        Widget&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;WebApp + Mobile Dev:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://demux.vektorsoft.com/demux/&quot;&gt;A Java framework that works on multiple platforms&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://asterclick.drclue.net/WBEA.html&quot;&gt;Allows for webapps on desktops&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;PhoneGap&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.mobilexweb.com/emulators&quot;&gt;Test mobile apps on desktop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Javascript optimizer: 
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.google.com/closure/&quot;&gt;closure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/mishoo/UglifyJS&quot;&gt;UglifyJS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en/Rhino_JavaScript_Compiler&quot;&gt;JS Compiler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Java 2 JS Toolkits: 
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://code.google.com/webtoolkit/&quot;&gt;WebToolKit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://j2s.sourceforge.net/&quot;&gt;J2S&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Python 2 JS Toolkigs:
&lt;a href=&quot;http://pyjs.org/&quot;&gt;PyJS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;JS Interpretr for command line: 
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.google.com/v8/&quot;&gt;v8&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://en.wikipedia.org/wiki/Nodejs&quot;&gt;NodeJS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://this-voice.org/alchemy/pride.html&quot;&gt;Android Alternative IDE&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Documentation around Syncing...&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://ericmiles.wordpress.com/2010/09/22/connecting-the-dots-with-android-syncadapter/&quot;&gt;sync adapter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://developer.android.com/resources/samples/SampleSyncAdapter/index.html&quot;&gt;Sample sync adapter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Other Notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Perki replacement that runs on Android.&lt;/li&gt;
&lt;li&gt;Use WebKit/PhoneGap + Javascript and HTML5&lt;/li&gt;
&lt;li&gt;Markdown library for Javascript&lt;/li&gt;
&lt;li&gt;Markdown editor for javscript&lt;/li&gt;
&lt;li&gt;TXGR converted to HTML5 Canvas&lt;/li&gt;
&lt;li&gt;How do we do background sync?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;More example code:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://code.google.com/p/jumpnote/&quot;&gt;jumpnote&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.java2s.com/Open-Source/Android/CatalogAndroid.htm&quot;&gt;CatalogAndroid&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We want to have it for Android, Linux and Windows.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://libreplanet.org/wiki/Group:Hardware/Howto_have_a_free_android_sdk&quot;&gt;Free Android SDK&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We need to research:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Alternative to freewrap
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://jsmooth.sourceforge.net/&quot;&gt;jsmooth&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://launch4j.sourceforge.net/&quot;&gt;launch4j&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.thisiscool.com/gcc_mingw.htm&quot;&gt;gcc mingw&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://vertis.github.com/2007/06/24/native-java-with-gcj-and-swt.html&quot;&gt;gcj+swt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://winrun4j.sourceforge.net/&quot;&gt;winrun4j&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Alternative to Canvas
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.piccolo2d.org/&quot;&gt;piccolo2d&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.jhotdraw.org/&quot;&gt;jhotdraw&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.manageability.org/blog/stuff/open-source-structured-graphics-libraries-in-java&quot;&gt;Contains an overview of options...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://jean-philippe.leboeuf.name/notebook/archives/000315.html&quot;&gt;Another overview of options&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Which Toolkit to use (SWT, Swing, AWT, etc)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A freewrap like tool for python:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://freecode.com/projects/pyinstaller&quot;&gt;pyiinstaller&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;More Android Dev options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PhoneGAP&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://kivy.org/&quot;&gt;Python, multi platform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://code.google.com/p/android-python27/w/list&quot;&gt;Python on android&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Resizing a Linux RAID</title>
<link href="https://www.0ink.net/posts/2014/2014-06-04-resizing-a-linux-raid.html"></link>
<id>urn:uuid:a676da51-278c-1911-9cf7-961feb4a87b3</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[It is possible to migrate the whole array to larger drives
(e.g. 250 GB to 1 TB) by replacing one by one. In the end the number
of devices will be the same, the data will remain intact, and you will
have more space available to you.
Extending an existing RAID array
In order to increase the usable size of the array, you must increase
...]]></summary>
<content type="html">&lt;p&gt;It is possible to migrate the whole array to larger drives
(e.g. 250 GB to 1 TB) by replacing one by one. In the end the number
of devices will be the same, the data will remain intact, and you will
have more space available to you.&lt;/p&gt;
&lt;h4 id=&quot;Extending+an+existing+RAID+array&quot; name=&quot;Extending+an+existing+RAID+array&quot;&gt;Extending an existing RAID array&lt;/h4&gt;
&lt;p&gt;In order to increase the usable size of the array, you must increase
the size of all disks in that array. Depending on the size of your
disks, this may take days to complete. It is also important to note
that while the array undergoes the resync process, it is vulnerable
to irrecoverable failure if another drive were to fail. It would (of
course) be a wise idea to completely back up your data before continuing.&lt;/p&gt;
&lt;p&gt;First, choose a drive and completely remove it from the array&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mdadm -f /dev/md0 /dev/sdd1
mdadm -r /dev/md0 /dev/sdd1&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, partition the new drive so that you are using the amount of
space you will eventually use on all new disks. For example, if you
are going from 100 GB drives to 250 GB drives, you will want to
partition the new 250 GB drive to use 250 GB, not 100 GB. Also,
remember to set the partition type to &lt;strong&gt;0xDA&lt;/strong&gt; - Non-fs data (or
&lt;strong&gt;0xFD&lt;/strong&gt;, Linux raid autodetect if you are still using the deprecated
autodetect).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fdisk /dev/sde&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now add the new disk to the array:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mdadm --add /dev/md0 /dev/sde1&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Allow the resync to fully complete before continuing. You will now
have to repeat the above steps for &lt;em&gt;&lt;strong&gt;each&lt;/strong&gt;\&lt;/em&gt; disk in your array.
Once all of the drives in your array have been replaced with larger
drives, we can grow the space on the array by issuing:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mdadm --grow /dev/md0 --size=max&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The array now represents one disk using all of the new available space.&lt;/p&gt;
&lt;p&gt;If the array has a write-intent bitmap, it is strongly recommended that
you remove the bitmap &lt;strong&gt;before&lt;/strong&gt; increasing the size of the array.
Failure to observe this precaution can lead to the destruction of the
array if the existing bitmap is insufficiently large, especially if
the increased array size necessitates a change to the bitmap&#039;s chunksize.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; mdadm --grow /dev/mdX --bitmap none
 mdadm --grow /dev/mdX --size max
 mdadm --wait /dev/mdX
 mdadm --grow /dev/mdX --bitmap internal&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the system relies on the disks in the array for booting the OS
(a common approach is to keep /boot in a RAID 1 array, i.e. md0,
across all the disks in the array) then you might need to manually
reinstall the bootloader on each of the new disks, because the array
synchronization does not sync the MBR. This should be done directly
on each disk and not on the array itself (/dev/mdX), and is safe to
do with the array online. For example, to re-install GRUB on the
first disk:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;grub
grub&amp;gt; root (hd0,0)
grub&amp;gt; setup (hd0)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You need to repeat this for each new disk that should contain the
bootloader. If you forget to do so, and find that you cannot boot
the system after replacing all the disks, you can boot from a rescue
CD/DVD/USB in order to install the bootloader as instructed above.&lt;/p&gt;
&lt;h4 id=&quot;Extending+the+filesystem&quot; name=&quot;Extending+the+filesystem&quot;&gt;Extending the filesystem&lt;/h4&gt;
&lt;p&gt;Now that you have expanded the underlying partition, you must now
resize your filesystem to take advantage of it.&lt;/p&gt;
&lt;p&gt;You may want to perform an fsck on the file system first to make sure
there are no underlying issues before attempting to resize the file system&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; fsck /dev/md0&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For an ext2/ext3 filesystem:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;resize2fs /dev/md0&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For a reiserfs filesystem:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;resize_reiserfs /dev/md0&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Please see filesystem documentation for other filesystems.&lt;/p&gt;
&lt;h4 id=&quot;LVM%3A+Growing+the+PV&quot; name=&quot;LVM%3A+Growing+the+PV&quot;&gt;LVM: Growing the PV&lt;/h4&gt;
&lt;p&gt;LVM (logical volume manager) abstracts a logical volume
(that a filesystem sits on) from the physical disk. If you are used
to LVM then you are likely used to growing LVs (logical volumes), but
what we grow here is the PV (physical volume) that sits on the
&lt;em&gt;md&lt;/em&gt; device (RAID array).&lt;/p&gt;
&lt;p&gt;For further LVM documentation, please see the
&lt;a href=&quot;http://tldp.org/HOWTO/LVM-HOWTO/&quot;&gt;Linux LVM HOWTO&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Growing the physical volume is trivial:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pvresize /dev/md0&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A before-and-after example is:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;root@barcelona:~# pvdisplay
  \-\-\- Physical volume ---
  PV Name               /dev/md0
  VG Name               server1_vg
  PV Size               931.01 GB / not usable 558.43 GB
  Allocatable           yes
  PE Size (KByte)       4096
  Total PE              95379
  Free PE               42849
  Allocated PE          52530
  PV UUID               BV0mGK-FRtQ-KTLv-aW3I-TllW-Pkiz-3yVPd1

root@barcelona:~# pvresize /dev/md0
  Physical volume &quot;/dev/md0&quot; changed
  1 physical volume(s) resized / 0 physical volume(s) not resized

root@barcelona:~# pvdisplay
  \-\-\- Physical volume ---
  PV Name               /dev/md0
  VG Name               server1_vg
  PV Size               931.01 GB / not usable 1.19 MB
  Allocatable           yes
  PE Size (KByte)       4096
  Total PE              238337
  Free PE               185807
  Allocated PE          52530
  PV UUID               BV0mGK-FRtQ-KTLv-aW3I-TllW-Pkiz-3yVPd1&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above is the PV part after md0 was grown from ~400GB to ~930GB
(a 400GB disk to a 1TB disk). Note the &lt;em&gt;PV Size&lt;/em&gt; descriptions before
and after.&lt;/p&gt;
&lt;p&gt;Once the PV has been grown (and hence the size of the VG, volume
group, will have increased), you can increase the size of an LV
(logical volume), and then finally the filesystem, eg:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;lvextend -L +50G -n home\_lv server1\_vg
resize2fs /dev/server1\_vg/home\_lv&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above grows the _home&lt;em&gt;lv&lt;/em&gt; logical volume in the _server1&lt;em&gt;vg&lt;/em&gt;
volume group by 50GB. It then grows the ext2/ext3 filesystem on that
LV to the full size of the LV, as per &lt;em&gt;Extending the filesystem&lt;/em&gt; above.&lt;/p&gt;
&lt;p&gt;Source: &lt;a href=&quot;https://raid.wiki.kernel.org/index.php/Growing&quot; title=&quot;Raid Wiki&quot;&gt;https://raid.wiki.kernel.org/index.php/Growing&lt;/a&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>Wi-Fi Sd Cards</title>
<link href="https://www.0ink.net/posts/2014/2014-05-11-wi-fi-sd-cards.html"></link>
<id>urn:uuid:8d87e03c-a7b6-881e-fd59-a4fb0b063078</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[My latest weekend project. Making a normal digital camera WIFI enabled.

With the Transcend Wi-Fi SD Card you can convert any digital camera into a Wi-Fi enable camera.
What I did here is to set it up so that it would automatically upload photos whenever I turn the camera on while at home.
The nice thing about this camera is that it runs a fully functional Linux environment within the card. The manufacturer was also nice enough to give you the opportunity to customize the card by running arbitrary shell scripts from the SD card itself.
My code is in github.
...]]></summary>
<content type="html">&lt;p&gt;My latest weekend project. Making a normal digital camera WIFI enabled.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2014/list_WIFISD.png&quot; alt=&quot;list_WIFISD.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;With the &lt;a href=&quot;http://www.transcend-info.com/products/Catlist.asp?FldNo=24&quot;&gt;Transcend Wi-Fi SD Card&lt;/a&gt; you can convert any digital camera into a Wi-Fi enable camera.&lt;/p&gt;
&lt;p&gt;What I did here is to set it up so that it would automatically upload photos whenever I turn the camera on while at home.&lt;/p&gt;
&lt;p&gt;The nice thing about this camera is that it runs a fully functional Linux environment within the card. The manufacturer was also nice enough to give you the opportunity to customize the card by running arbitrary shell scripts from the SD card itself.&lt;/p&gt;
&lt;p&gt;My code is in &lt;a href=&quot;https://github.com/alejandroliu/sdwifi&quot;&gt;github&lt;/a&gt;.&lt;/p&gt;</content>
</entry>
<entry>
<title>Raspberry Pi Weekend project</title>
<link href="https://www.0ink.net/posts/2014/2014-04-26-raspberry-pi-weekend-project.html"></link>
<id>urn:uuid:1194f112-8151-1c5c-b8b6-58c5c7fdff47</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[So finally took the time to try out a Raspberry Pi. For this weekend project wanted to do something relatively simple.
Essentially, I wanted to recreate/enhance the functionality of a
TL-WR702N.

The TL-WR702N Nano Router is a neat device but being closed, can not be customized to what I wanted. It can be used in
the following modes:
...]]></summary>
<content type="html">&lt;p&gt;So finally took the time to try out a Raspberry Pi. For this weekend project wanted to do something &lt;em&gt;relatively&lt;/em&gt; simple.
Essentially, I wanted to recreate/enhance the functionality of a
&lt;a href=&quot;http://www.tp-link.com/en/products/details/?model=TL-WR702N&quot;&gt;TL-WR702N&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2014/TL-WR720N-01.jpg&quot; alt=&quot;tl-wr702n-01&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The TL-WR702N Nano Router is a neat device but being closed, can not be customized to what I wanted. It can be used in
the following modes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AP&lt;/li&gt;
&lt;li&gt;Client&lt;/li&gt;
&lt;li&gt;Repeater&lt;/li&gt;
&lt;li&gt;Router&lt;/li&gt;
&lt;li&gt;Bridge&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Specifically I was interested in the bridge mode. However, rather
than bridging from one SSID to another SSID, I wanted to &lt;em&gt;route/nat&lt;/em&gt;
between the two. So in theory, should be simple to implement (as the
hardware should have all the necessary components) but not allowed by
the software.&lt;/p&gt;
&lt;h1&gt;Enter the Raspberry Pi.&lt;/h1&gt;
&lt;p&gt;So the Pi, is a mini computer that can be loaded with any software you want. The B-model, has a built-in Ethernet and USB ports to plug-in &lt;em&gt;two&lt;/em&gt; WIFI adaptors. For this functionality I am using the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Raspberry-Pi Model-B
&lt;img src=&quot;https://upload.wikimedia.org/wikipedia/commons/thumb/6/6f/Raspberry_Pi_B%2B_top.jpg/300px-Raspberry_Pi_B%2B_top.jpg&quot; alt=&quot;Raspberry Pi&quot; /&gt;&lt;/li&gt;
&lt;li&gt;WIFI stick (2 units)
&lt;img src=&quot;/images/2014/993655_LB_00_FB.EPS_250.jpg&quot; alt=&quot;WIFI&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For the software I am using:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/gamaral/rpi-buildroot&quot;&gt;Raspberry Pi buildroot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.realtek.com.tw/downloads/downloadsView.aspx?Langid=1&amp;amp;PNid=21&amp;amp;PFid=48&amp;amp;Level=5&amp;amp;Conn=4&amp;amp;DownTypeID=3&amp;amp;GetDown=false&amp;amp;Downloads=true&quot;&gt;hostapd-rtl8192cu from Realtek&lt;/a&gt;
You need to get the RTL8188CUS package for Linux.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So you need two WIFI adaptors, as one will not work as master and slave at the same time. Essentially, one WIFI interface will act as the client WIFI station. The other WIFI interface acts as a WIFI hotspot. I chose to use &lt;code&gt;buildroot&lt;/code&gt; instead of a normal Linux distro like &lt;a href=&quot;http://www.raspbian.org/&quot;&gt;Raspbian&lt;/a&gt; or &lt;a href=&quot;http://archlinuxarm.org/platforms/armv6/raspberry-pi&quot;&gt;Arch Linux Arm&lt;/a&gt; because I wanted to run it as an embedded system. Normal Linux distro&#039;s are supposed to be properly &lt;em&gt;shutdown&lt;/em&gt; and would complain when you simply yank the power cord. The &lt;code&gt;buildroot&lt;/code&gt; image I have is customized so that the file system is always mounted read-only. It will switch to read-write only to write persistent data and then switch back to read-only. The normal &lt;code&gt;hostapd&lt;/code&gt; that comes with &lt;code&gt;buildroot&lt;/code&gt; is the normal open source project and does not come with the &lt;code&gt;rtl8192cu&lt;/code&gt; driver. You need to download and build the &lt;code&gt;Realtek&lt;/code&gt; version. For this to work, I did the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a start-up scripts that set the whole thing up.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;sets-up the filesystem&lt;/li&gt;
&lt;li&gt;starts &lt;code&gt;syslog&lt;/code&gt;, &lt;code&gt;sshd&lt;/code&gt;, &lt;code&gt;rngd&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;sets-up &lt;code&gt;eth0&lt;/code&gt; and &lt;code&gt;wlan0&lt;/code&gt; to be configured by &lt;code&gt;ifplugd&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;starts &lt;code&gt;wpa_supplicant&lt;/code&gt; on &lt;code&gt;wlan0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;starts &lt;code&gt;httpd&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;start and configure &lt;code&gt;wlan1&lt;/code&gt; as an Access Point.&lt;/li&gt;
&lt;li&gt;start and configure &lt;code&gt;dnsmasq&lt;/code&gt; for DNS and DHCP.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Wrote a small web UI to configure the WIFI client.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;All this stuff can be found in &lt;a href=&quot;https://github.com/alejandroliu/harpy&quot;&gt;github&lt;/a&gt;.&lt;/p&gt;</content>
</entry>
<entry>
<title>DVD archiving</title>
<link href="https://www.0ink.net/posts/2014/2014-03-11-dvd-archiving.html"></link>
<id>urn:uuid:cdba3c01-4834-013a-d96e-318784f11cfe</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This is my simple procedure for backing up my DVD movies:
Examine the DVD:
dvdbackup -i /dev/sr0 -I
Create a full backup:
dvdbackup -i /dev/dvd -o ~ -M
Creating an ISO:
...]]></summary>
<content type="html">&lt;p&gt;This is my simple procedure for backing up my DVD movies:&lt;/p&gt;
&lt;p&gt;Examine the DVD:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dvdbackup -i /dev/sr0 -I&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a full backup:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dvdbackup -i /dev/dvd -o ~ -M&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Creating an ISO:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkisofs -dvd-video -udf -o ~/dvd.iso ~/movie_name&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Testing the newly created ISO:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mplayer dvd:// -dvd-device ~/dvd.iso&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>Private vs. Personal</title>
<link href="https://www.0ink.net/posts/2014/2014-01-26-private-vs-personal.html"></link>
<id>urn:uuid:137b8501-9e65-bb1c-9049-051938e710dd</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[In Microsoft Outlook has the option to tag e-mails with a sensitivity tag. Technically this is fairly meaningless. However sometimes I like to use them.  
The confidential tag is quite self explanatory. I always confuse what is the difference between private and personal.   So here is one possibility...

Personal information are things like preferences, political association, likes and dislikes.
Private information are things like bank account numbers. Stuff that you probably would like to keep secret.

...]]></summary>
<content type="html">&lt;p&gt;In Microsoft Outlook has the option to tag e-mails with a sensitivity tag. Technically this is fairly meaningless. However sometimes I like to use them.  &lt;/p&gt;
&lt;p&gt;The confidential tag is quite self explanatory. I always confuse what is the difference between private and personal.   So here is one possibility...&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Personal information are things like preferences, political association, likes and dislikes.&lt;/li&gt;
&lt;li&gt;Private information are things like bank account numbers. Stuff that you probably would like to keep secret.&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Cleaning-up Outlook Calendar</title>
<link href="https://www.0ink.net/posts/2013/2013-12-23-cleaning-up-outlook-calendar.html"></link>
<id>urn:uuid:a0c69720-fe01-d50f-6282-2ee04427d1a9</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This is a procedure I go through at the end of the year
to clean-up my Outlook Calendar.  Usually the Outlook
Calendar gets full of junk over time.  So this is something
worth doing on a regular basis.
Procedure for Outlook 2007

...]]></summary>
<content type="html">&lt;p&gt;This is a procedure I go through at the end of the year
to clean-up my Outlook Calendar.  Usually the Outlook
Calendar gets full of junk over time.  So this is something
worth doing on a regular basis.&lt;/p&gt;
&lt;h2&gt;Procedure for Outlook 2007&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Backup calendar folder&lt;/li&gt;
&lt;li&gt;Select default calendar&lt;/li&gt;
&lt;li&gt;Switch view to &lt;code&gt;Inactive Appointments (non-recurrent)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Delete appointments&lt;/li&gt;
&lt;li&gt;Switch view to &lt;code&gt;Inactive Appointments (recurrent)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Delete appointments&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Procedures for Previous versions of Outlook&lt;/h2&gt;
&lt;p&gt;This is my procedure for cleaning my Outlook calendar from old appointments and other assorted outdated stuff:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Backup your calendar folder (just in case)&lt;/li&gt;
&lt;li&gt;Create a temporary Calendar folder&lt;/li&gt;
&lt;li&gt;Select your default Calendar&lt;/li&gt;
&lt;li&gt;Switch to &lt;code&gt;All Appointments&lt;/code&gt; view:
&lt;ul&gt;
&lt;li&gt;View -&amp;gt; Current View -&amp;gt; All Appointments&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Select all the appointments and &lt;strong&gt;move&lt;/strong&gt; them to the temporary Calendar folder&lt;/li&gt;
&lt;li&gt;Select the temporary Calendar folder&lt;/li&gt;
&lt;li&gt;Switch to &lt;code&gt;Active Appointments&lt;/code&gt; vew:
&lt;ul&gt;
&lt;li&gt;View -&amp;gt; Current View -&amp;gt; Active Appointments&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Select all the visible appointments and &lt;strong&gt;move&lt;/strong&gt; the m back to the default Calendar folder.&lt;/li&gt;
&lt;li&gt;You can now dispose of the temporary folder (or backed it up for reference.&lt;/li&gt;
&lt;/ol&gt;</content>
</entry>
<entry>
<title>Chrome Kerberos Authentication</title>
<link href="https://www.0ink.net/posts/2013/2013-12-02-chrome-kerberos-authentication.html"></link>
<id>urn:uuid:5676154d-bd90-f308-6d1f-14e70db7a6cb</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[To config chrome to use kerberos authentication you need to start the application the following parameter:

auth-server-whitelist - Allowed FQDN - Set the FQDN of the IdP Server. Example:

chrome --auth-server-whitelist="*aai-logon.domain-a.com"
auth-negotiate-delegate-whitelist - For which FQDN credential delegation will be allowed.
...]]></summary>
<content type="html">&lt;p&gt;To config chrome to use kerberos authentication you need to start the application the following parameter:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;auth-server-whitelist - Allowed FQDN - Set the FQDN of the IdP Server. Example:

chrome --auth-server-whitelist=&quot;*aai-logon.domain-a.com&quot;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;auth-negotiate-delegate-whitelist - For which FQDN credential delegation will be allowed.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;References:

&lt;/p&gt;</content>
</entry>
<entry>
<title>Deploying Chrome Extensions</title>
<link href="https://www.0ink.net/posts/2013/2013-11-30-deploying-chrome-extensions.html"></link>
<id>urn:uuid:80ebca9c-aab4-5c53-622c-c5bfd38688d5</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[The following links outline how to deploy Chrome extensions in a enterprise manner:

Installing Chrome Extensions
Other Deployment Options
Force Installing Extensions

...]]></summary>
<content type="html">&lt;p&gt;The following links outline how to deploy Chrome extensions in a enterprise manner:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://support.google.com/chrome/a/answer/188453?hl=en&quot;&gt;Installing Chrome Extensions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://developer.chrome.com/extensions/external_extensions.html&quot;&gt;Other Deployment Options&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.guidingtech.com/14503/force-install-extensions-scripts-chrome-not-on-web-store/&quot;&gt;Force Installing Extensions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>My Must Have Android Apps</title>
<link href="https://www.0ink.net/posts/2013/2013-11-08-my-must-have-android-apps.html"></link>
<id>urn:uuid:cf0fe745-7c80-61bf-a168-9d13fc172ae0</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This is a list of my favorite Android Apps:
Essentials

Barcode Scanner - Play Store F-Droid
Ghost Commander - F-Droid
F-Droid Alternative Application Manager. Usually Open source stuff with significantly less crap ware and ads.
...]]></summary>
<content type="html">&lt;p&gt;This is a list of my favorite Android Apps:&lt;/p&gt;
&lt;h2 id=&quot;Essentials&quot; name=&quot;Essentials&quot;&gt;Essentials&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Barcode Scanner - &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.google.zxing.client.android&quot;&gt;Play Store&lt;/a&gt; &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=com.google.zxing.client.android&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Ghost Commander - &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=com.ghostsq.commander&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://f-droid.org/&quot;&gt;F-Droid&lt;/a&gt; Alternative Application Manager. Usually Open source stuff with significantly less crap ware and ads.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Productivity&quot; name=&quot;Productivity&quot;&gt;Productivity&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;WordPress - &lt;a href=&quot;http://market.android.com/details?id=org.wordpress.android&quot;&gt;PlayS tore&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;KeePassDroid - &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=com.android.keepass&quot;&gt;f-droid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;GoTasks - &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.mile.android.gotasks&quot;&gt;Play Store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Dropbox - &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.dropbox.android&quot;&gt;Play Store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Quickoffice - &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.quickoffice.android&quot;&gt;Play Store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;SimpleNote - &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.automattic.simplenote&quot;&gt;Play Store&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Social&quot; name=&quot;Social&quot;&gt;Social&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Facebook - &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.facebook.katana&quot;&gt;Play Store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Facebook Messenger &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.facebook.orca&quot;&gt;Play Store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;LinkedIn &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.linkedin.android&quot;&gt;Play Store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Skype - &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.skype.raider&quot;&gt;Play store&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Travel&quot; name=&quot;Travel&quot;&gt;Travel&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;KLM - &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.afklm.mobile.android.gomobile.klm&quot;&gt;Play Store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;My Tracks - &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.google.android.maps.mytracks&quot;&gt;Play Store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;TEST&lt;/em&gt; Wikivoyage offline - Travel guide. &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=org.github.OxygenGuide&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Tools&quot; name=&quot;Tools&quot;&gt;Tools&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;KPN HotSpots - &lt;a href=&quot;https://play.google.com/store/apps/details?id=nl.kpn.hotspot&quot;&gt;Play Store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Timer - &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=org.dpadgett.timer&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Yahoo Weather - &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.yahoo.mobile.client.android.weather&quot;&gt;Play Store&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Special+use&quot; name=&quot;Special+use&quot;&gt;Special use&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Searchlight - Flashlight App. &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=com.scottmain.android.searchlight&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Floating Image - Photo frame &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=dk.nindroid.rss&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Worldclock - &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=com.irahul.worldclock&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Diagnostics&quot; name=&quot;Diagnostics&quot;&gt;Diagnostics&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;TEST&lt;/em&gt; List my apps - &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=de.onyxbits.listmyapps&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;TEST&lt;/em&gt; List Apps - &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=net.sourceforge.andsys&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;TEST&lt;/em&gt; Internet Call settings - &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=eu.siebeck.sipswitch&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Root+stuff&quot; name=&quot;Root+stuff&quot;&gt;Root stuff&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Root Verifier - &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=com.abcdjdj.rootverifier&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;No Frills CPU Control - &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=it.sineo.android.noFrillsCPUClassic&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Performance Control - &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=com.brewcrewfoo.performance&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;oandbackup - &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=dk.jens.backup&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Premium&quot; name=&quot;Premium&quot;&gt;Premium&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;GPS Test Plus - &lt;a href=&quot;http://market.android.com/details?id=com.chartcross.gpstestplus&quot;&gt;Play Store&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.access_company.graffiti_pro&quot;&gt;Graffiti Pro for Android&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;TEST&lt;/em&gt; Signal Booster - &lt;a href=&quot;http://market.android.com/details?id=com.s4bb.signalbooster&quot;&gt;Play Store&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Kids&quot; name=&quot;Kids&quot;&gt;Kids&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;PlusMinusTimesDivide - &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=eu.lavarde.pmtd&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;MidiSheetMusic - &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=com.midisheetmusic&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Learn Music Notes - &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=net.fercanet.LNM&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Stock+Experience+and+Alternatives&quot; name=&quot;Stock+Experience+and+Alternatives&quot;&gt;Stock Experience and Alternatives&lt;/h2&gt;
&lt;p&gt;Used to replace crapware for something closer to the Android experience or to complement incomplete ROMs.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Holo Locker &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.mobint.locker&quot;&gt;Play Store&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Holo Launcher &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.mobint.hololauncher&quot;&gt;Play Store&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Contacts+ &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.contapps.android&quot;&gt;Play Store&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;AOSP Calendar - &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=org.sufficientlysecure.standalonecalendar&quot;&gt;F-Droid&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Stock Music Player &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=com.android.music&quot;&gt;F-Droid&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Launcher3 &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=com.android.launcher3&quot;&gt;F-Droid&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Evaluate&quot; name=&quot;Evaluate&quot;&gt;Evaluate&lt;/h2&gt;
&lt;h3 id=&quot;RSS+reader&quot; name=&quot;RSS+reader&quot;&gt;RSS reader&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Tiny Tiny RSS - &lt;a href=&quot;http://market.android.com/details?id=org.fox.ttrss&quot;&gt;Play Store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;TTRSS-Reader - &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=org.ttrssreader&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Drawing&quot; name=&quot;Drawing&quot;&gt;Drawing&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Markers - &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=org.dsandler.apps.markers&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Text+editors&quot; name=&quot;Text+editors&quot;&gt;Text editors&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;TED - &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=fr.xgouchet.texteditor&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Text Edit - &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=org.paulmach.textedit&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Turbo Editor - &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=com.vmihalachi.turboeditor&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Readers&quot; name=&quot;Readers&quot;&gt;Readers&lt;/h3&gt;
&lt;p&gt;need to support: Epub, Chm, Pdf, CBR/CBZ reader&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Page Turner - &lt;a href=&quot;http://www.pageturner-reader.org/for-readers/features/&quot;&gt;Web site&lt;/a&gt; EPUB (Does page location synchronisation)&lt;/li&gt;
&lt;li&gt;CoolReader: &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=org.coolreader&quot;&gt;F-Droid&lt;/a&gt; epub, chm fb2, txt, rtf,tcr, html&lt;/li&gt;
&lt;li&gt;APV PDF Viewer: &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=cx.hell.android.pdfview&quot;&gt;F-Droid&lt;/a&gt; pdf&lt;/li&gt;
&lt;li&gt;Document Viewer: &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=org.sufficientlysecure.viewer&quot;&gt;F-Droid&lt;/a&gt; pdf, cbz djvu, xps,fb2&lt;/li&gt;
&lt;li&gt;VuDroid: &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=org.vudroid&quot;&gt;F-Droid&lt;/a&gt; pdf, djvu&lt;/li&gt;
&lt;li&gt;ACV: &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=net.androidcomics.acv&quot;&gt;F-Droid&lt;/a&gt; cbz, jpeg, png, bmp, folders&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Multimedia&quot; name=&quot;Multimedia&quot;&gt;Multimedia&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;XBMC Remote - &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=org.xbmc.android.remote&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;UPNP Player - &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=de.yaacc&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;AMPlayer - For an &lt;a href=&quot;https://github.com/ampache/ampache&quot;&gt;Ampache server&lt;/a&gt; &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=com.orphan.amplayer&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;ServeStream - &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=net.sourceforge.servestream&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Misc&quot; name=&quot;Misc&quot;&gt;Misc&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Box - &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.box.android&quot;&gt;Play Store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Bump - &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.bumptech.bumpga&quot;&gt;Play Store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Daily Money - &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=com.bottleworks.dailymoney&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;HotSpot Login - &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=net.sf.andhsli.hotspotlogin&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Linphone - &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=org.linphone&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;LinConnect - Send notifications to desktop &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=com.willhauck.linconnectclient&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Serval Mesh - &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=org.servalproject&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Read-it later poche - &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=fr.gaulupeau.apps.Poche&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;SSH client &amp;amp; Terminal Emulator - &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=sk.vx.connectbot&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;VNC client - &lt;a href=&quot;https://f-droid.org/repository/browse/?fdcategory=System&amp;amp;fdid=android.androidVNC&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Omnidroid automation - &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=edu.nyu.cs.omnidroid.app&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Wifi Analyzer - &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.farproc.wifi.analyzer&quot;&gt;Play Store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Solitaire - &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.kmagic.solitaire&quot;&gt;Play Store&lt;/a&gt; &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=com.kmagic.solitaire&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;ReGalAndroid - Client for G2/G3/Meanlto Gallery and Pwigo. &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=net.dahanne.android.regalandroid&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;DroidFish - &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=org.petero.droidfish&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Evaluate+for+Group+Contacts%2FCalendar&quot; name=&quot;Evaluate+for+Group+Contacts%2FCalendar&quot;&gt;Evaluate for Group Contacts/Calendar&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;aCal - &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=com.morphoss.acal&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;CalDAV Sync Adapter - &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=org.gege.caldavsyncadapter&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;DAVdroid - &lt;a href=&quot;https://f-droid.org/repository/browse/?ffdid=at.bitfire.davdroid&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Kolab Client- Dev Preview &lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=at.dasz.KolabDroid&quot;&gt;F-Droid&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Wishlist%3F&quot; name=&quot;Wishlist%3F&quot;&gt;Wishlist?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Tetris&lt;/li&gt;
&lt;li&gt;Sudoku&lt;/li&gt;
&lt;li&gt;RPGs?&lt;/li&gt;
&lt;li&gt;Tricorder&lt;/li&gt;
&lt;li&gt;Push to talk&lt;/li&gt;
&lt;li&gt;Emulators&lt;/li&gt;
&lt;li&gt;Android remote control&lt;/li&gt;
&lt;li&gt;wifi talkie or search 4 talkie&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Alternative Keyboards&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=de.onyxbits.remotekeyboard&quot;&gt;Remote Keyboard&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For older Android versions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=com.appengine.paranoid_android.lost&quot;&gt;Contact Owner&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://f-droid.org/repository/browse/?fdid=net.szym.barnacle&quot;&gt;Barnacle Wifi Tether&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.handcent.nextsms&quot;&gt;Handcent SMS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Other things to check out:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://freecode.com/projects/wifix-lite&quot;&gt;Fix WIFI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://readwrite.com/2012/05/23/5-push-to-talk-apps-that-turn-your-smartphone-into-a-walkie-talkie&quot;&gt;Push to talk&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://freecode.com/projects/simple-mtpfs&quot;&gt;MTPFS?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://freecode.com/projects/night-time-display&quot;&gt;Clock&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://freecode.com/projects/copy-sync-paste&quot;&gt;Sync clipboards&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://freecode.com/projects/mo-da-browser&quot;&gt;HTML5 client&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://freecode.com/projects/remote-keyboard&quot;&gt;Remote Kbd&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.phonearena.com/news/How-to-make-your-Android-phones-notifications-appear-on-your-computer-desktop_id50461&quot;&gt;Send Notifications to Desktop&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>wp-cron and cron</title>
<link href="https://www.0ink.net/posts/2013/2013-11-01-wp-cron-and-cron.html"></link>
<id>urn:uuid:f066674e-ad88-bc3c-26b3-af151e96e1b8</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Normal WordPress operation has a cron like functionality that runs scheduled tasks as users visit the blog.
It is possible to replace this with a standalone cron (like UNIX cron).
To disable the &quot;webcron&quot; (i.e. trigerring tasks as URLs are visited) add to your wp-config.php the following:
 define('DISABLE_WP_CRON', true);

Then call this from cron:
...]]></summary>
<content type="html">&lt;p&gt;Normal WordPress operation has a cron like functionality that runs scheduled tasks as users visit the blog.&lt;/p&gt;
&lt;p&gt;It is possible to replace this with a standalone cron (like UNIX cron).&lt;/p&gt;
&lt;p&gt;To disable the &amp;quot;webcron&amp;quot; (i.e. trigerring tasks as URLs are visited) add to your &lt;code&gt;wp-config.php&lt;/code&gt; the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; define(&#039;DISABLE_WP_CRON&#039;, true);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then call this from cron:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; curl http://example.com/wp-cron.php
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Optionally you could call &lt;code&gt;wp-cron.php&lt;/code&gt; using the &lt;code&gt;php-cli&lt;/code&gt; executable.&lt;/p&gt;</content>
</entry>
<entry>
<title>Using wget with given IP/vhost</title>
<link href="https://www.0ink.net/posts/2013/2013-10-31-using-wget-with-given-ipvhost.html"></link>
<id>urn:uuid:0a8409f2-6398-6a2e-b9b9-afbc94fd10f4</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This is one neat trick.  For vhosts you can connect with an IP yet provide the right host name with the following:
 wget http://1.1.1.1/  --header 'Host: www.example.com'

...]]></summary>
<content type="html">&lt;p&gt;This is one neat trick.  For vhosts you can connect with an IP yet provide the right host name with the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; wget http://1.1.1.1/  --header &#039;Host: www.example.com&#039;
&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>Using a NAS200 as a Print server</title>
<link href="https://www.0ink.net/posts/2013/2013-10-22-using-nas-200.html"></link>
<id>urn:uuid:7db2b820-d4d2-1534-cf33-5d74cc0eb60e</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Last weekend I had a small weekend project to move my All-In-One Printer/Scanner from my Xen host server to a spare NAS200 I had lying around. Since the NAS200 has a i486 compatible CPU, and I had been able to run a CentOS 5 distro before, I figure it would make a good server with low power consumption.

For that I updated my NASCC firmware so that it would boot a USB key, and update my CentOS image creation script. This worked well, I was able to boot CentOS without that much effort altogether.
I myself have an Epson Stylus CX5500 which unfortunately only comes with binary drivers. This was not much of a problem since the NAS200 has a i486 compatible CPU. I find this is relatively unique among different NAS models.
Alas, the performance was quite disappointing. I should be used to the NAS200 underperforming. But really, this was truly sad. I did not bother to test the printing, but I did try scanning with it. Running scanimage to scan a single page was taking over 15 minutes before I hit Ctrl+C.
It was an idea, but the results were so sub par. The only take-aways of this are:
...]]></summary>
<content type="html">&lt;p&gt;Last weekend I had a small weekend project to move my All-In-One Printer/Scanner from my Xen host server to a spare NAS200 I had lying around. Since the NAS200 has a i486 compatible CPU, and I had been able to run a CentOS 5 distro before, I figure it would make a good server with low power consumption.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2013/linksys-nas200.jpg&quot; alt=&quot;nas200&quot; /&gt;&lt;/p&gt;
&lt;p&gt;For that I updated my &lt;a href=&quot;http://nascc.sf.net&quot;&gt;NASCC firmware&lt;/a&gt; so that it would boot a USB key, and update my CentOS image creation &lt;a href=&quot;https://sourceforge.net/p/nascc/wiki/centos/&quot;&gt;script&lt;/a&gt;. This worked well, I was able to boot CentOS without &lt;em&gt;that much&lt;/em&gt; effort altogether.&lt;/p&gt;
&lt;p&gt;I myself have an &lt;a href=&quot;http://www.cnet.com.au/epson-stylus-cx5500-339283304.htm&quot;&gt;Epson Stylus CX5500&lt;/a&gt; which unfortunately only comes with &lt;a href=&quot;http://download.ebz.epson.net/dsc/search/01/search/?OSC=LX&quot;&gt;binary drivers&lt;/a&gt;. This was not much of a problem since the NAS200 has a i486 compatible CPU. I find this is relatively unique among different NAS models.&lt;/p&gt;
&lt;p&gt;Alas, the performance was quite disappointing. I should be used to the NAS200 underperforming. But really, this was truly sad. I did not bother to test the printing, but I did try scanning with it. Running &lt;code&gt;scanimage&lt;/code&gt; to scan a single page was taking over 15 minutes before I hit &lt;code&gt;Ctrl+C&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It was an idea, but the results were so sub par. The only take-aways of this are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I was able to run open source as well as binary blobs on a NAS200 relatively easily.&lt;/li&gt;
&lt;li&gt;I was able to use CentOS5 pretty much out-of-the box. No recompiles required. Did notice though that &lt;code&gt;cups&lt;/code&gt; would seg-fault. My guess is that the i386 package some how got some i686 optimizations on it.&lt;/li&gt;
&lt;li&gt;My &lt;a href=&quot;https://sourceforge.net/projects/nascc/files/LEC/&quot;&gt;Linux Ethernet Console&lt;/a&gt; made a very good network console. I was able to troubleshoot some very early boot problems with it.&lt;/li&gt;
&lt;li&gt;NAS200 performance for scanning was abysmal.&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>UNIX find with dates</title>
<link href="https://www.0ink.net/posts/2013/2013-10-14-unix-find-with-dates.html"></link>
<id>urn:uuid:67037a00-4e3f-97e3-5979-017c50d7fadc</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[-atime/-ctime/-mtime the last time a files's access time, file status and modification time, measured in days or minutes. Time interval in options -ctime, -mtime and -atime is an integer with optional sign.

n: If the integer n does not have sign this means exactly n days ago, 0 means today.
+n: if it has plus sing, then it means more then n days ago, or older then n,
-n: if it has the minus sign, then it means less than n days ago (-n), or younger then n. It's evident that -1 and 0 are the same and both mean today.

...]]></summary>
<content type="html">&lt;p&gt;&lt;code&gt;-atime/-ctime/-mtime&lt;/code&gt; the last time a files&#039;s &lt;em&gt;access time&lt;/em&gt;, &lt;em&gt;file status&lt;/em&gt; and &lt;em&gt;modification time&lt;/em&gt;, measured in days or minutes. Time interval in options &lt;code&gt;-ctime&lt;/code&gt;, &lt;code&gt;-mtime&lt;/code&gt; and &lt;code&gt;-atime&lt;/code&gt; is an integer with optional sign.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;n&lt;/em&gt;: If the integer &lt;em&gt;n&lt;/em&gt; does not have sign this means exactly &lt;em&gt;n&lt;/em&gt; days ago, &lt;code&gt;0&lt;/code&gt; means today.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;+n&lt;/em&gt;: if it has &lt;code&gt;plus&lt;/code&gt; sing, then it means &lt;em&gt;more then &lt;strong&gt;n&lt;/strong&gt; days ago&lt;/em&gt;, or older then &lt;em&gt;n&lt;/em&gt;,&lt;/li&gt;
&lt;li&gt;&lt;em&gt;-n&lt;/em&gt;: if it has the &lt;code&gt;minus&lt;/code&gt; sign, then it means &lt;em&gt;less than &lt;strong&gt;n&lt;/strong&gt; days ago (-n)&lt;/em&gt;, or younger then &lt;em&gt;n&lt;/em&gt;. It&#039;s evident that &lt;code&gt;-1&lt;/code&gt; and &lt;code&gt;0&lt;/code&gt; are the same and both mean &lt;em&gt;today&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Examples%3A&quot; name=&quot;Examples%3A&quot;&gt;Examples:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Find everything in your home directory modified in the last 24 hours: &lt;code&gt;$ find $HOME -mtime 0&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Find everything in your home directory modified in the last 7 days: &lt;code&gt;$ find $HOME -mtime -7&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Find everything in your home directory that have &lt;strong&gt;NOT&lt;/strong&gt; been modified in the last year: &lt;code&gt;$ find $HOME -mtime +365&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To find html files that have been modified in the last seven days, I can use -mtime with the argument -7 (include the hyphen): &lt;code&gt;$ find . -mtime -7 -name &quot;*.html&quot; -print&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you use the number &lt;code&gt;7&lt;/code&gt; (without a hyphen), find will match only html files that were modified exactly seven days ago:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; `$ find . -mtime 7 -name &quot;*.html&quot; -print`
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;To find those html files that I haven&#039;t touched for at least 7 days, I use &lt;code&gt;+7&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ find . -mtime +7 -name &quot;*.html&quot; -print&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Enable local file caching for NFS share on Linux</title>
<link href="https://www.0ink.net/posts/2013/2013-10-07-enable-local-file-caching-for-nfs-share-on-linux.html"></link>
<id>urn:uuid:0fc9b10a-12fa-add2-891c-d46c2f210da8</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[In Linux, there is a caching filesystem called FS-Cache which enables
file caching for network file systems such as NFS. FS-Cache is built
into the Linux kernel 2.6.30 and higher. In order for FS-Cache to
operate, it needs cache back-end which provides actual storage for
caching. One such cache back-end is cachefiles. Therefore, once you
set up cachefiles, it will automatically enable file caching for NFS shares.
...]]></summary>
<content type="html">&lt;p&gt;In Linux, there is a caching filesystem called &lt;code&gt;FS-Cache&lt;/code&gt; which enables
file caching for network file systems such as NFS. &lt;code&gt;FS-Cache&lt;/code&gt; is built
into the Linux kernel 2.6.30 and higher. In order for &lt;code&gt;FS-Cache&lt;/code&gt; to
operate, it needs cache back-end which provides actual storage for
caching. One such cache back-end is &lt;code&gt;cachefiles&lt;/code&gt;. Therefore, once you
set up &lt;code&gt;cachefiles&lt;/code&gt;, it will automatically enable file caching for NFS shares.&lt;/p&gt;
&lt;h2 id=&quot;Requirements&quot; name=&quot;Requirements&quot;&gt;Requirements&lt;/h2&gt;
&lt;p&gt;One requirement for setting up &lt;code&gt;cachefiles&lt;/code&gt; is that local filesystem support user-defined extended file attributes (i.e., &lt;code&gt;xattr&lt;/code&gt;), because &lt;code&gt;cachefiles&lt;/code&gt; use &lt;code&gt;xattr&lt;/code&gt; to store extra information for cache maintenance. If your local filesystem is ext4-type, you don&#039;t need to worry about this since &lt;code&gt;xattr&lt;/code&gt; is enabled in ext4 by default. However, if you are using ext3 filesystem, then you need to mount the local filesystem with &amp;quot;user_xattr&amp;quot; option. To do so, edit /etc/mtab to add &amp;quot;user_xattr&amp;quot; mount option to the disk partition that will be used by &lt;code&gt;cachefiles&lt;/code&gt; for file caching. For example, assuming that /dev/hda1 is such a partition:&lt;/p&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;/dev/hda1 / ext3 rw,user_xattr  0 0
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;After modifying /etc/fstab, reload it by running:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo mount -o remount / 
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Configure+CacheFiles&quot; name=&quot;Configure+CacheFiles&quot;&gt;Configure CacheFiles&lt;/h2&gt;
&lt;p&gt;In order to set up cache back-end using &lt;code&gt;cachefiles&lt;/code&gt;, you need to install &lt;code&gt;cachefilesd&lt;/code&gt;, a userspace daemon for managing &lt;code&gt;cachefiles&lt;/code&gt;. To install &lt;code&gt;cachefilesd&lt;/code&gt; on Ubuntu or Debian:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo apt-get install cachefilesd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To install &lt;code&gt;cachefilesd&lt;/code&gt; on CentOS, Fedora or RedHat:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo yum install cachefilesd
$ sudo chkconfig cachefilesd on
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After installation, enable &lt;code&gt;cachefilesd&lt;/code&gt; by editing its configuration file as follows.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo vi /etc/default/cachefilesd
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;RUN=yes
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;Next, mount a remote NFS share with &lt;code&gt;fsc&lt;/code&gt; option:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; $ sudo vi /etc/fstab
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt; 192.168.1.13:/home/xmodulo /mnt nfs rw,hard,intr,fsc
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;Alternatively, if you mount the remote NFS share from the command line, specify &lt;code&gt;fsc&lt;/code&gt; as a command-line option:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo mount -t nfs 192.168.1.13:/home/xmodulo /mnt -o fsc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, restart &lt;code&gt;cachefilesd&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo service cachefilesd restart
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point, file caching should be enabled for the mounted NFS share, which means that previously accessed files in the mounted NFS share will be retrieved from local file cache. If you want to flush NFS file cache for any reason, simply restart &lt;code&gt;cachefilesd&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; $ sudo service cachefilesd restart 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Source: &lt;a href=&quot;http://xmodulo.com/2013/06/how-to-enable-local-file-caching-for-nfs-share-on-linux.html&quot;&gt;xmodule.com&lt;/a&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>sdf.org</title>
<link href="https://www.0ink.net/posts/2013/2013-09-30-sdf-org.html"></link>
<id>urn:uuid:65069855-d9e2-1fb6-6a99-731860126d08</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[sdf.org
This one is an interesting site.
The Super Dimension Fortress is a networked community of free software authors, teachers, librarians, students, researchers, hobbyists, computer enthusiasts, the aural and visually impaired. It is operated as a recognized non-profit 501(c)(7) and is supported by its members.
Our mission is to provide remotely accessible computing facilities for the advancement of public education, cultural enrichment, scientific research and recreation. Members can interact electronically with each other regardless of their location using passive or interactive forums.  Further purposes include the recreational exchange of information
concerning the Liberal and Fine Arts.
Members have UNIX shell access to games, email, usenet, chat, bboard, webspace, gopherspace, programming utilities, archivers, browsers, and more. The SDF community is made up of caring, highly skilled people who operate behind the scenes to maintain a non-commercial INTERNET.
...]]></summary>
<content type="html">&lt;p&gt;&lt;a href=&quot;http://sdf.org/&quot;&gt;sdf.org&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This one is an interesting site.&lt;/p&gt;
&lt;p&gt;The Super Dimension Fortress is a networked community of free software authors, teachers, librarians, students, researchers, hobbyists, computer enthusiasts, the aural and visually impaired. It is operated as a recognized non-profit 501(c)(7) and is supported by its members.&lt;/p&gt;
&lt;p&gt;Our mission is to provide remotely accessible computing facilities for the advancement of public education, cultural enrichment, scientific research and recreation. Members can interact electronically with each other regardless of their location using passive or interactive forums.  Further purposes include the recreational exchange of information
concerning the Liberal and Fine Arts.&lt;/p&gt;
&lt;p&gt;Members have UNIX shell access to games, email, usenet, chat, bboard, webspace, gopherspace, programming utilities, archivers, browsers, and more. The SDF community is made up of caring, highly skilled people who operate behind the scenes to maintain a non-commercial INTERNET.&lt;/p&gt;</content>
</entry>
<entry>
<title>Driving Continuous Integration from Git</title>
<link href="https://www.0ink.net/posts/2013/2013-09-22-continous-integration-from-git.html"></link>
<id>urn:uuid:727913cb-cdde-33f7-274c-fca7b20f1a11</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Testing, code coverage, style enforcement are all check-in and merge
requirements that can be automated and driven from Git.
If you're among the rising number of Git users out there, you're in
luck: You can automate pieces of your development workflow with Git
hooks. Hooks are a native Git mechanism for firing off custom scripts
before or after certain operations such as commit, merge, applypatch,
...]]></summary>
<content type="html">&lt;p&gt;&lt;strong&gt;Testing, code coverage, style enforcement are all check-in and merge
requirements that can be automated and driven from Git.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you&#039;re among the rising number of Git users out there, you&#039;re in
luck: You can automate pieces of your development workflow with Git
hooks. Hooks are a native Git mechanism for firing off custom scripts
before or after certain operations such as commit, merge, applypatch,
and others. Think of them as henchmen for your Git repo. Pre-operation
hooks act as bouncers, guarding your repo with a velvet rope. And
post-operation hooks are your Man Friday, faithfully carrying out
follow-up tasks on your behalf.&lt;/p&gt;
&lt;p&gt;Installing hooks for a Git repository is fairly straightforward, and
&lt;a href=&quot;http://git-scm.com/book/en/Customizing-Git-Git-Hooks&quot;&gt;well-documented&lt;/a&gt;.
In this article, we focus on using Git hooks to augment continuous
integration practices, starting with an example that makes combining
Git and continuous integration (CI) less painful. The code is written
in Ruby. Fortunately, Ruby is a language that highly prizes readability,
so even if you don&#039;t know Ruby, you can easily follow along.&lt;/p&gt;
&lt;h2 id=&quot;Automate+CI+Configuration+for+Git+Branches&quot; name=&quot;Automate+CI+Configuration+for+Git+Branches&quot;&gt;Automate CI Configuration for Git Branches&lt;/h2&gt;
&lt;p&gt;One of the blessings of Git is how easy it is to branch off and develop
in isolation. This means the master stays releasable, you get the
freedom to experiment, and your teammates aren&#039;t derailed if code from
the experimentation proves to be half-baked. One challenge of Git,
however, is how many branches a team ends up with ? scores of active
branches, most of which live for only a few days. Who is going to take
the time to set up continuous integration for all those piddly little
branches? Your henchmen, that&#039;s who.&lt;/p&gt;
&lt;p&gt;To automatically apply CI to new development branches, you&#039;ll use the
&amp;quot;post-receive&amp;quot; hook type. These are server-side hooks, triggered after
pushes to the repository are completed. In such cases, you can use the
post-receive hook to fire off a script that programmatically clones a
master&#039;s CI configs and applies them to new branches using the CI
server&#039;s exposed API. It might look something like this, when using
the open-source and hugely popular &lt;a href=&quot;http://www.jenkins-ci.org/&quot;&gt;Jenkins&lt;/a&gt;
CI server:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;#!/usr/bin/env ruby

 # Ref update hook for creating new Jenkins job 
 # configurations for newly pushed branches.
 #
 # requires Ruby 1.9.3+ 

 require &#039;yaml&#039;
 require &#039;net/https&#039;
 require &#039;uri&#039;
 require &#039;rexml/document&#039;
 include REXML

 # load ci-config.yml from hook directory
 def load_config
     hookDir = File.expand_path File.dirname(__FILE__)
     configPath = hookDir + &quot;/ci-config.yml&quot;
     puts configPath
     raise &quot;No ci-config.yml found.&quot; unless File.exists? configPath
     YAML.load_file(configPath)
 end

 # Grab the configured Jenkins server
 config = load_config
 raise &quot;ci-config.yml file is incomplete: missing jenkins_server&quot; unless
     config[&quot;jenkins_server&quot;]
 server = config[&quot;jenkins_server&quot;]
 raise &quot;ci-config.yml file is incomplete: username, password, url and
     default_job are required for jenkins_server&quot; unless
         server[&#039;url&#039;] and server[&#039;username&#039;] and
         server[&#039;password&#039;] and server[&#039;default_job&#039;]

 # iterate through updated refs looking for new branches
 ARGF.readlines.each { |line|
     args = line.split
     oldVal = args[0]
     newVal = args[1]
     ref = args[2]

     if /^0{40}$/.match(oldVal) and ref.start_with?(&quot;refs/heads/&quot;) 
         # new branch!
         # retrieve the jenkins job config
         # TODO only need to do this once!
         uri = URI.parse(
            &quot;#{server[&#039;url&#039;]}/job/#{server[&#039;default_job&#039;]}/config.xml&quot;)
         req = Net::HTTP::Get.new(uri.to_s)
         req.basic_auth server[&#039;username&#039;], server[&#039;password&#039;]
         http = Net::HTTP.new(uri.host, uri.port)
         http.verify_mode = OpenSSL::SSL::VERIFY_NONE
         http.use_ssl = uri.scheme.eql?(&quot;https&quot;)

         # execute the request
         response = http.start {|http| http.request(req)}

         raise &quot;Bad response from jenkins, is your ci-config.yml correct?&quot;
             unless response.is_a? Net::HTTPOK

         # parse the config.xml from the response
         doc = Document.new response.body
         doc.root.get_elements(
             &quot;//branches/hudson.plugins.git.BranchSpec/name&quot;).each { 
                 # overwrite branch to be our new ref
                 |elem| elem.text = ref 
         }

         # create a new request to upload the modified config.xml
         newJob = &quot;&quot;
         doc.write newJob

         newJobName = ref[&quot;refs/heads/&quot;.length..-1].gsub(&quot;/&quot;, &quot;-&quot;)
         uri = URI.parse(&quot;#{server[&#039;url&#039;]}/createItem?name=#{newJobName}&quot;)        
         req = Net::HTTP::Post.new(uri.to_s, 
         initheader = {&#039;Content-Type&#039; =&amp;gt; &#039;application/xml&#039;})

         req.basic_auth server[&#039;username&#039;], server[&#039;password&#039;]
         req.body = newJob

         # upload the new job
         response = http.start {|http| http.request(req)}
         raise &quot;Failed to post new job to jenkins&quot; unless
             response.is_a? Net::HTTPOK
     end
 }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this hook in place, you need only push a dev branch to the repo,
and it will automatically be put under test. (It&#039;s possible to run CI
builds against branches using a build parameter to represent the
target branch, but that muddles the build history. The cloning approach
provides a clean, clear history.) Applying every last facet of the CI
scheme to branches isn&#039;t necessary ? for example, running each and
every branch through the load test gamut might be overkill. But even
if you skip the load and UI tests, and run just unit and API- or
integration-level tests, these are huge wins.&lt;/p&gt;
&lt;p&gt;The risk of introducing defects into master is greatly reduced by
testing on the branch before merging. Developers can also work more
efficiently and confidently because of the frequent feedback on
changes (instead of the old merge-then-pray technique). And for teams
who include testing as part of their definition of &amp;quot;done,&amp;quot; managers
and scrum master types catch a break. With the Git hook automatically
putting branch code under test, the team&#039;s practices and values are
being enforced without the need for nag-mails or raised eyebrows during
stand-up.&lt;/p&gt;
&lt;h2 id=&quot;Vet+Merges+to+Master&quot; name=&quot;Vet+Merges+to+Master&quot;&gt;Vet Merges to Master&lt;/h2&gt;
&lt;p&gt;Two hallmarks of coding craftsmanship are an affinity for automated
tests, and adherence to stylistic rules (such as avoiding empty
try/catch blocks or duplicated code). Despite best intentions, everyone
neglects best practices from time to time. That&#039;s where Git hooks come
in. Pre-receive hooks living in the central repository qualify incoming
pushes, making sure they&#039;re good enough to get past the velvet rope.
Let&#039;s look at three hooks designed to protect master from slip-ups made
on development branches.&lt;/p&gt;
&lt;h2 id=&quot;Require+Passing+Branch+Builds&quot; name=&quot;Require+Passing+Branch+Builds&quot;&gt;Require Passing Branch Builds&lt;/h2&gt;
&lt;p&gt;The whole point of working on a development branch is to isolate
yourself and create a space to experiment (read: &amp;quot;break stuff&amp;quot;). So
it&#039;s natural to see failing tests on the branch while development is
in progress. When it&#039;s time to merge to master, however, things had
better be tidied up. This can be enforced programmatically with a hook
that checks to see whether the incoming push is a merge to master, and
if so, verify that all tests are passing on the branch before
processing the merge.&lt;/p&gt;
&lt;p&gt;If you happen to be using Bamboo, you can cleanly fetch test results
for a given commit. If you use Jenkins or its predecessor, &lt;a href=&quot;http://www.hudson-ci.org/&quot;&gt;Hudson&lt;/a&gt;,
you can fetch a set of recent build results then parse through them
to see which builds ran against the commit in question. (This hook,
and those that follow are implemented for the Bamboo CI server, but
they can be implemented in more or less the same way on all CI
servers.)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;#!/usr/bin/env ruby

 # Ref update hook for verifying the build status of 
 # a topic branch being merged into 
 # a protected branch (e.g. master) from a Bamboo server.
 #
 # requires Ruby 1.9.3+ 

 require_relative &#039;ci-util&#039;
 require &#039;json&#039;

 # parse args supplied by git: &amp;lt;ref_name&amp;gt; &amp;lt;old_sha&amp;gt; &amp;lt;new_sha&amp;gt;
 ref = simple_branch_name ARGV[0]
 prevCommit = ARGV[1]
 newCommit = ARGV[2]

 # test if the updated ref is one we want to enforce green 
 # builds for exit_if_not_protected_ref(ref)

 # get the tip of the most recently merged branch
 tip_of_merged_branch = 
    find_newest_non_merge_commit(prevCommit, newCommit)

 # parse our Bamboo server config
 bamboo = read_config(&quot;bamboo&quot;, [&quot;url&quot;, &quot;username&quot;, &quot;password&quot;])

 # query Bamboo for build results
 response = httpGet(
    bamboo, 
    &quot;/rest/api/latest/result/byChangeset/#{tip_of_merged_branch}.json&quot;)
 body = JSON.parse(response.body)        
 # tally the results
 failed = successful = in_progress = 0
 body[&#039;results&#039;][&#039;result&#039;].collect { |result|
     case result[&#039;state&#039;]
     when &quot;Failed&quot;
       failed += 1
     when &quot;Successful&quot;
       successful += 1
     when &quot;Unknown&quot;
       if result[&#039;lifeCycleState&#039;] == &quot;InProgress&quot;
         in_progress += 1
       end
     end
 }

 # display a short message describing the build status for 
 #the merged branch and abort if necessary
 if failed &amp;gt; 0
     # at least one red build - block the branch update
     abort &quot;#{shortSha(tip_of_merged_branch)} has #{failed} 
     red #{pluralize(failed, &#039;build&#039;, &#039;builds&#039;)}.&quot;
 elsif in_progress &amp;gt; 0
     # at least one incomplete build - block the branch update
     abort &quot;#{shortSha(tip_of_merged_branch)} has #{in_progress}
     #{pluralize(in_progress, &#039;build&#039;, &#039;builds&#039;)} that have not
     completed yet.&quot;
 else   
     # all green builds - allow the branch update
     puts &quot;#{shortSha(tip_of_merged_branch)} has #{successful} 
         green #{pluralize(successful, &#039;build&#039;, &#039;builds&#039;)}.&quot;
 end&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Enforce+Code+Coverage+Requirements&quot; name=&quot;Enforce+Code+Coverage+Requirements&quot;&gt;Enforce Code Coverage Requirements&lt;/h2&gt;
&lt;p&gt;Along with successful test runs, you want to make sure that new code
added on development branches is tested as thoroughly as code already
on master. This ensures that the overall test coverage level of the
project doesn&#039;t drop when a development branch is merged back in. This,
too, can be checked with Git hooks.&lt;/p&gt;
&lt;p&gt;A simple Git hook can verify that coverage on the branch meets the
minimum threshold. To enforce this, a hook can be created to compare
the coverage rate on master with that of the branch, and reject the
merge if the branch&#039;s coverage is inferior.&lt;/p&gt;
&lt;p&gt;Most CI servers don&#039;t expose code coverage data through their remote
APIs. But there&#039;s an easy work-around: pulling down the code coverage
report. To do this, the build must be configured to publish the report
as a shared artifact, both on master and on the branch build. (Notice
how automatically cloning build configs for development branches comes
in handy here: set it up for master, and get it on the branch for free!)
Once published, you can get the latest coverage report from master by
a call to the CI server. For branch coverage, you can fetch the
coverage report either from the latest build, or for builds related to
the reference (commit) being merged, as shown here for the code
coverage tool Clover.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;#!/usr/bin/env ruby

 # Ref update hook for asserting the code coverage of a 
 # topic branch being merged into a 
 # protected branch (e.g. master) is the same or better
 #
 # requires Ruby 1.9.3+ 

 require_relative &#039;ci-util&#039;
 require &#039;rexml/document&#039;

 include REXML

 # Determine the code coverage for a particular commit by 
 # parsing Clover artifacts
 def find_coverage(bamboo, commit)
     # grab the clover.xml artifact from the build. 
     # This (assumes a shared artifact named 
     # &#039;clover&#039; with &#039;clover.xml&#039; at the root).
     # Change this for your coverage tool?s report name.
     clover_xml = shared_artifact_for_commit(bamboo, commit,
         bamboo[&quot;coverage_key&quot;], &quot;clover/clover.xml&quot;)
     doc = Document.new clover_xml

     # parse out the project metrics element from the response
     metrics = XPath.first(doc, &quot;coverage/project/metrics&quot;)

     # Use algorithm similar to Clover 
     # (https://confluence.atlassian.com/x/LoHEB) for 
     # determining coverage percentage
     covered_elements = 
         metrics.attribute(&quot;coveredconditionals&quot;).value.to_i
     covered_elements += 
         metrics.attribute(&quot;coveredmethods&quot;).value.to_i
     covered_elements += 
         metrics.attribute(&quot;coveredstatements&quot;).value.to_i

     elements = metrics.attribute(&quot;conditionals&quot;).value.to_i
     elements += metrics.attribute(&quot;methods&quot;).value.to_i
     elements += metrics.attribute(&quot;statements&quot;).value.to_i

     coverage = 0
     if (elements &amp;gt; 0)
         coverage = covered_elements / elements
     end
     coverage
 end

 # parse args supplied by git: &amp;lt;ref_name&amp;gt; &amp;lt;old_sha&amp;gt; &amp;lt;new_sha&amp;gt;
 ref = simple_branch_name ARGV[0]
 prevCommit = ARGV[1]
 newCommit = ARGV[2]

 # test if the updated ref is one we want to enforce 
 # green builds for  exit_if_not_protected_ref(ref)

 # get the tip of the most recently merged branch
 tip_of_merged_branch = 
     find_newest_non_merge_commit(prevCommit, newCommit)

 # parse our bamboo server config
 bamboo = read_config(&quot;bamboo&quot;, 
     [&quot;url&quot;, &quot;username&quot;, &quot;password&quot;, &quot;coverage_key&quot;])

 # calculate code coverage for the old and new commits
 prev_coverage = find_coverage(bamboo, prevCommit)
 new_coverage = find_coverage(bamboo, tip_of_merged_branch)

 # if the coverage has dropped for the new commit, block the update
 if prev_coverage &amp;gt; new_coverage
     abort &quot;Code coverage for #{shortSha(tip_of_merged_branch)} is 
         only #{new_coverage}! #{ref} is currently at #{prev_coverage}.&quot; 
 else
     # if the coverage has increased, TFCIT
     puts &quot;Nice work! Code coverage for #{ref} has 
         increased by #{new_coverage - prev_coverage}.&quot;
 end&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Enforce+Good+Coding+Style&quot; name=&quot;Enforce+Good+Coding+Style&quot;&gt;Enforce Good Coding Style&lt;/h2&gt;
&lt;p&gt;Tests are something no self-respecting software project can do without,
but they only tell part of the story. Open source tools such as
&lt;a href=&quot;http://checkstyle.sourceforge.net/&quot;&gt;Checkstyle&lt;/a&gt; and
&lt;a href=&quot;http://findbugs.sourceforge.net/&quot;&gt;Findbugs&lt;/a&gt; scour your codebase and
provide reports on stylistic violations ? anything from duplicated
code to excessively long methods to the use of deprecated methods.
These are hard-won guidelines, and they exist for a reason: Ignoring
them can result in code being harder to understand, harder to maintain,
and more vulnerable to runtime problems.&lt;/p&gt;
&lt;p&gt;As with code coverage, each team has a different level of tolerance
for unstylish code. But introducing more style violations is almost
universally agreed-upon as undesirable. In this, Git hooks come to the
rescue. Build artifacts come into play here as well since you can
easily retrieve the violations report. (No CI server we&#039;re aware of
exposes static analysis data via remote access API.) So you can create
another pre-receive hook that checks violations for master and the dev
branch, and rejects the push if it would introduce additional errors
into master.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;#!/usr/bin/env ruby

 # Ref update hook for asserting that a topic branch 
 # being merged into a protected
 # branch (e.g. master) does not introduce an increase in 
 # checkstyle violations
 #
 # requires Ruby 1.9.3+ 

 require_relative &#039;ci-util&#039;
 require &#039;rexml/document&#039;

 include REXML

 # This example 
 def count_checkstyle_violations(bamboo, commit)
     # grab the checkstyle.xml artifact from the 
     # build (assumes a shared artifact named 
     # &#039;checkstyle&#039; with &#039;checkstyle-result.xml&#039; at the root)
     checkstyle_xml = 
         shared_artifact_for_commit(bamboo, commit, 
         bamboo[&quot;checkstyle_key&quot;],
        &quot;checkstyle/checkstyle-result.xml&quot;)
     doc = Document.new checkstyle_xml
     # could go to town on the comparison here - but let&#039;s just count  
     # the raw number of errors for the time being
     XPath.match(doc, &quot;//error&quot;).length
 end

 # parse args supplied by git: &amp;lt;ref_name&amp;gt; &amp;lt;old_sha&amp;gt; &amp;lt;new_sha&amp;gt;
 ref = simple_branch_name ARGV[0]
 prevCommit = ARGV[1]
 newCommit = ARGV[2]

 # test if the updated ref is one we want to enforce green builds for
 exit_if_not_protected_ref(ref)

 # get the tip of the most recently merged branch
 tip_of_merged_branch = 
    find_newest_non_merge_commit(prevCommit, newCommit)

 # parse our bamboo server config
 bamboo = read_config(&quot;bamboo&quot;, 
    [&quot;url&quot;, &quot;username&quot;, &quot;password&quot;, &quot;checkstyle_key&quot;])

 # calculate number of checkstyle violations for 
 #the old and new commits
 prev_violations = 
     count_checkstyle_violations(bamboo, prevCommit)
 new_violations = 
     count_checkstyle_violations(bamboo, tip_of_merged_branch)

 # if the number of checkstyle violations has increased, block the update
 if prev_violations &amp;gt; new_violations
     abort &quot;#{shortSha(tip_of_merged_branch)} 
        has #{new_violations} checkstyle violations! #{ref} 
        currently has only #{prev_violations}.&quot; 
 else
     # if the number of checkstyle violations has 
     # decreased, send kudos to the dev
     puts &quot;Nice work! #{ref} has #{new_violations - prev_violations} 
         fewer checkstyle violations than before.&quot;
 end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To get the original source code and surrounding config files for all
the server-side hooks you&#039;ve seen here, clone the repo at:
&lt;a href=&quot;https://bitbucket.org/tpettersen/git-ci-hooks&quot;&gt;bitbucket.org&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;Think+Globally%2C+Hook+Locally&quot; name=&quot;Think+Globally%2C+Hook+Locally&quot;&gt;Think Globally, Hook Locally&lt;/h2&gt;
&lt;p&gt;We know that the sooner an issue is discovered, the easier (and faster
and cheaper) it is to fix. That&#039;s why hooks that operate on local
clones of a repository are so useful: They offer immediate feedback.
Because we don&#039;t get the cmd prompt back until a hook completes,
client-side hooks should be limited to operations that take only a few
seconds, lest the development flow be interrupted. Let&#039;s look at two
hooks that complete almost instantly.&lt;/p&gt;
&lt;h2 id=&quot;Get+Branch+Build+Status&quot; name=&quot;Get+Branch+Build+Status&quot;&gt;Get Branch Build Status&lt;/h2&gt;
&lt;p&gt;Exposing branch build status in the terminal window with a
post-checkout hook catches two fish with one worm: It provides
actionable information, and eliminates the need to switch applications
to get it. Upon checkout (and remember, in Git &amp;quot;checkout&amp;quot; means
switching branches, not pulling down code as with SVN and Perforce),
this hook grabs the branch&#039;s head revision number from the local copy.
It then queries the CI server to see whether that revision has been
built, and if so, whether the build succeeded.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;#!/usr/bin/env ruby

 # post-checkout hook for determining the build status of the 
 # checked out ref from the CI server.
 #
 # Requires Ruby 1.9.3+ 

 require &#039;yaml&#039;
 require &#039;json&#039;
 require &#039;net/https&#039;
 require &#039;uri&#039;

 # utility for correctly pluralizing quantities
 def pluralize count, single, multiple
   count == 1 ? single : multiple
 end

 # parse args supplied by git
 ref = ARGV[1]       # ref being checked out
 isBranch = ARGV[2]  # 0 = file checkout, 1 = branch checkout

 # we only care about branch checkouts 
 if isBranch == &quot;1&quot;
   # initialise build status counts
   failed = successful = in_progress = 0

   # loop through each configured Stash server, retrieving build 
   # statuses for the checked out commit and 
   # counting the number of failed, successful and in progress builds
   hookDir = File.expand_path File.dirname(__FILE__)
   configPath = hookDir + &quot;/bamboo-config.yml&quot;
   raise &quot;No bamboo-config.yml found.&quot; unless File.exists? configPath
   config = YAML.load_file(configPath)
   raise &quot;bamboo-config.yml file is incomplete: 
       username, password &amp;amp; url are required&quot; unless
       config[&#039;url&#039;] and config[&#039;username&#039;] and config[&#039;password&#039;]

   # normalize base url
   baseUrl = config[&#039;url&#039;]
   # assume https if no scheme spcified
   if not baseUrl.start_with? &quot;http&quot;
     baseUrl = &quot;https://#{baseUrl}&quot;
   end
   # strip trailing slashes
   while baseUrl.end_with? &quot;/&quot;
     baseUrl = baseUrl[0..-2]
   end

   # prepare a request to hit the build status REST end-point
   build_status_resource = 
       &quot;#{baseUrl}/rest/api/latest/result/byChangeset&quot;
   uri = URI.parse(&quot;#{build_status_resource}/#{ref}&quot;)
   req = Net::HTTP::Get.new(uri.to_s, initheader = 
       {&#039;Content-Type&#039; =&amp;gt; &#039;application/json&#039;, 
           &#039;Accept&#039; =&amp;gt; &#039;application/json&#039;})
   req.basic_auth config[&#039;username&#039;], config[&#039;password&#039;]
   http = Net::HTTP.new(uri.host, uri.port)
   http.verify_mode = OpenSSL::SSL::VERIFY_NONE
   http.use_ssl = uri.scheme.eql?(&quot;https&quot;)

   # execute the request
   response = http.start {|http| http.request(req)}

   if not response.is_a? Net::HTTPOK
     puts &#039;An unknown error occurred while querying 
         Bamboo for build results.&#039;    
     exit    
   else 
     # if the request succeeded, count 
     # the statuses from the response
     body = JSON.parse(response.body)        
     body[&#039;results&#039;][&#039;result&#039;].collect { |result|
       case result[&#039;state&#039;]
       when &quot;Failed&quot;
           failed += 1
       when &quot;Successful&quot;
           successful += 1
       when &quot;Unknown&quot;
           if result[&#039;lifeCycleState&#039;] == &quot;InProgress&quot;
             in_progress += 1
           end
       end
     }
   end   

   # display a short message describing the build status 
   # for the checked out commit
   shortRef = ref[0..7]
   if failed &amp;gt; 0
     puts &quot;Warning! #{shortRef} has #{failed} 
          red #{pluralize(failed, &#039;build&#039;, &#039;builds&#039;)} 
          (plus #{successful} green and #{in_progress} 
          in progress).\nDetails: #{uri}&quot;
   elsif successful == 0
       puts &quot;#{shortRef} hasn&#039;t built yet.&quot;
   else
        puts &quot;#{shortRef} has #{successful} green 
            #{pluralize(successful, &#039;build&#039;, &#039;builds&#039;)}.&quot;
   end

 end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If, for example, the hook tells you the head commit on the master has
built successfully, then it&#039;s a &amp;quot;safe&amp;quot; commit to create a feature
branch from. Or let&#039;s say the hook says the build for that revision
failed, yet the team&#039;s wallboard shows a green build for that branch
(or vice versa). That means the local copy is out-of-date. Whether to
pull down the updates is determined on a case-by-case basis.&lt;/p&gt;
&lt;p&gt;This hook and its config files can be found at &lt;a href=&quot;https://bitbucket.org/tpettersen/post-checkout-build-status&quot;&gt;bitbucket&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;Sanity-Check+Code+Style&quot; name=&quot;Sanity-Check+Code+Style&quot;&gt;Sanity-Check Code Style&lt;/h2&gt;
&lt;p&gt;Checking for violations at merge time is great, but a pre-commit hook
analyzing the changeset keeps the style police off your back entirely.
Start by capturing the names of files being updated or added and
concatenating them. That string of file names is then passed into the
Checkstyle run command. If violations are found, the commit is rejected.&lt;/p&gt;
&lt;p&gt;Note that despite variations between them, all static analysis tools
can be used with this approach. Findbugs, for example, must be run on
the entire project because it looks at methods referenced across
classes. But that&#039;s not necessarily a deal-breaker. Small and
medium-sized projects can be fully analyzed quickly, especially if
a generous heap space is allocated to the process.&lt;/p&gt;
&lt;h2 id=&quot;Come+As+You+Are&quot; name=&quot;Come+As+You+Are&quot;&gt;Come As You Are&lt;/h2&gt;
&lt;p&gt;All the ideas presented here are vendor-neutral. Git hooks may not
revolutionize software development the way continuous integration
has, but every time a task, practice or rule is automated, it&#039;s a
win.&lt;/p&gt;
&lt;p&gt;From &lt;a href=&quot;http://www.drdobbs.com/architecture-and-design/driving-continuous-integration-from-git/240161383&quot;&gt;Dr. Dobbs Journal&lt;/a&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>Off site backup options</title>
<link href="https://www.0ink.net/posts/2013/2013-09-19-off-site-backup-options.html"></link>
<id>urn:uuid:0f97ed5e-3a47-8e75-a5c9-71d146429a31</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This is my working notes on doing off-site backups to the cloud.
Still trying to figure out where to keep Offsite backups.
These are the candidates:



...]]></summary>
<content type="html">&lt;p&gt;This is my working notes on doing off-site backups to the cloud.
Still trying to figure out where to keep Offsite backups.&lt;/p&gt;
&lt;p&gt;These are the candidates:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Site&lt;/th&gt;
&lt;th&gt;Free Quota&lt;/th&gt;
&lt;th&gt;100GB/Yr&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AltDrive&lt;/td&gt;
&lt;td&gt;30 day&lt;/td&gt;
&lt;td&gt;USD 45&lt;/td&gt;
&lt;td&gt;Unlimited, Linux binary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iDrive&lt;/td&gt;
&lt;td&gt;5GB&lt;/td&gt;
&lt;td&gt;USD 6&lt;/td&gt;
&lt;td&gt;Starts at 1TB, Linux binary, API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pCloud&lt;/td&gt;
&lt;td&gt;20GB&lt;/td&gt;
&lt;td&gt;USD 10&lt;/td&gt;
&lt;td&gt;Starts at 500GB, Binary, Rest API, WebDAV&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DropBox&lt;/td&gt;
&lt;td&gt;2GB&lt;/td&gt;
&lt;td&gt;USD 12&lt;/td&gt;
&lt;td&gt;Starts at 1TB, ZYPKG available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CopyCom&lt;/td&gt;
&lt;td&gt;15GB&lt;/td&gt;
&lt;td&gt;USD 20&lt;/td&gt;
&lt;td&gt;starts at 250GB, binary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MEGA&lt;/td&gt;
&lt;td&gt;50GB&lt;/td&gt;
&lt;td&gt;USD 20&lt;/td&gt;
&lt;td&gt;Linux client, Starts at 500GB.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google&lt;/td&gt;
&lt;td&gt;15GB&lt;/td&gt;
&lt;td&gt;USD 24&lt;/td&gt;
&lt;td&gt;ZYPKG available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SkyDrive&lt;/td&gt;
&lt;td&gt;15GB&lt;/td&gt;
&lt;td&gt;USD 24&lt;/td&gt;
&lt;td&gt;WebDav&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MemoPal&lt;/td&gt;
&lt;td&gt;3GB&lt;/td&gt;
&lt;td&gt;USD 25&lt;/td&gt;
&lt;td&gt;WebDav, ZYPKG available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ADrive&lt;/td&gt;
&lt;td&gt;60 days&lt;/td&gt;
&lt;td&gt;USD 25&lt;/td&gt;
&lt;td&gt;FTP or WebDAV&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MemoPal&lt;/td&gt;
&lt;td&gt;3GB&lt;/td&gt;
&lt;td&gt;USD 25&lt;/td&gt;
&lt;td&gt;starts at 200GB, Binary or WebDav, ZYPKG available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iDriveSync&lt;/td&gt;
&lt;td&gt;5GB&lt;/td&gt;
&lt;td&gt;USD 33&lt;/td&gt;
&lt;td&gt;WebDav&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amazon S3&lt;/td&gt;
&lt;td&gt;5GB/1yr&lt;/td&gt;
&lt;td&gt;USD 36&lt;/td&gt;
&lt;td&gt;pay-per-use, REST API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;box.com&lt;/td&gt;
&lt;td&gt;10GB&lt;/td&gt;
&lt;td&gt;USD 48&lt;/td&gt;
&lt;td&gt;Uses WebDAV&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Crashplan&lt;/td&gt;
&lt;td&gt;1 month&lt;/td&gt;
&lt;td&gt;USD 48&lt;/td&gt;
&lt;td&gt;Unlimited, Binary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OtherDrive&lt;/td&gt;
&lt;td&gt;2GB&lt;/td&gt;
&lt;td&gt;USD 55&lt;/td&gt;
&lt;td&gt;Java client&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4shared&lt;/td&gt;
&lt;td&gt;15GB&lt;/td&gt;
&lt;td&gt;USD 78&lt;/td&gt;
&lt;td&gt;WebDAV + FTP, Max 100GB?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CloudMe&lt;/td&gt;
&lt;td&gt;3GB&lt;/td&gt;
&lt;td&gt;USD 96&lt;/td&gt;
&lt;td&gt;WebDAV&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;Todo&quot; name=&quot;Todo&quot;&gt;Todo&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;checkout google drive and dropbox.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is another tool to compare vendors &lt;a href=&quot;http://www.cloudwards.net/articles/online-backup/&quot;&gt;CloudWards&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;iDrive API info:
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/idrivevangelist&quot;&gt;Sample code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://evs.idrive.com/web-developers-guide.htm&quot;&gt;Reference&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>No Comment</title>
<link href="https://www.0ink.net/posts/2013/2013-09-19-no-comment.html"></link>
<id>urn:uuid:4b2cb150-420e-e956-a18d-d7e54f562285</id>
<updated>2022-11-02T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[
This should require no explanation...
...]]></summary>
<content type="html">&lt;p&gt;&lt;img src=&quot;/images/2013/leadership.jpg&quot; alt=&quot;About Leadership&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This should require no explanation...&lt;/p&gt;</content>
</entry>
<entry>
<title>Alarm Notification</title>
<link href="https://www.0ink.net/posts/2013/2013-09-19-alarm-notification.html"></link>
<id>urn:uuid:567023cd-5920-685c-96da-c3e83713a984</id>
<updated>2022-01-10T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This tutorial describes how to use the alarm manager to set alarms and how to use the notification framework to display them. In short, the sequence goes like this:

In an Activity AlarmManager.set is called with a PendingIntent containing a Uri.
When the alarm goes off, the Uri is called triggering a BroadcastReceiver.
In the BroadcastReceiver NotificationManager.notify is called with a PendingIntent.
When the notification is clicked, the Activity in the PendingIntent is started.
...]]></summary>
<content type="html">&lt;p&gt;This tutorial describes how to use the alarm manager to set alarms and how to use the notification framework to display them. In short, the sequence goes like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;In an Activity AlarmManager.set is called with a PendingIntent containing a Uri.&lt;/li&gt;
&lt;li&gt;When the alarm goes off, the Uri is called triggering a BroadcastReceiver.&lt;/li&gt;
&lt;li&gt;In the BroadcastReceiver NotificationManager.notify is called with a PendingIntent.&lt;/li&gt;
&lt;li&gt;When the notification is clicked, the Activity in the PendingIntent is started.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;Alarm+Manager&quot; name=&quot;Alarm+Manager&quot;&gt;Alarm Manager&lt;/h2&gt;
&lt;p&gt;The Alarm Manager is a SystemService so it should be gotten like this&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&#039;s set method can take parameters to define when, how and what to set off for the alarm. To set an absolute time and have it go off even if the device is on stand-by, use the &lt;code&gt;RTC_WAKEUP&lt;/code&gt; type. The PendingIntent parameter is what gets called when the alarm goes off. Unless you want an Activity to start when the alarm goes off, a broadcast-type intent should be used like this&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PendingIntent pendingintent = PendingIntent.getBroadcast(Activity.this, 0, intent, Intent.FLAG_GRANT_READ_URI_PERMISSION);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The intent parameter can hold a Uri which can contain some information about what the alarm is all about.&lt;/p&gt;
&lt;h2 id=&quot;BroadcastReceiver+and+NotificationManager&quot; name=&quot;BroadcastReceiver+and+NotificationManager&quot;&gt;BroadcastReceiver and NotificationManager&lt;/h2&gt;
&lt;p&gt;The BroadcastReceiver must be defined in the manifest.xml like this&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    &amp;lt;receiver
        android:name=&quot;package.AlarmReceiver&quot;
    &amp;gt;
        &amp;lt;intent-filter&amp;gt;
            &amp;lt;action
                android:name=&quot;intentname&quot; /&amp;gt;
            &amp;lt;data
                android:scheme=&quot;myscheme&quot; /&amp;gt;
        &amp;lt;/intent-filter&amp;gt;
    &amp;lt;/receiver&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The intentname and myscheme values must match the name used in the intent and the scheme used in the uri in the intent. In the onReceive method, the notification is started to show the user the alarm went off. The Notification Manager is also a SystemService, get it like this&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a new Notification object with an icon, a title and the time (probably &lt;code&gt;System.currentTimeMillis()&lt;/code&gt;) Set some flags into the defaults like this&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;notification.defaults |= Notification.DEFAULT_SOUND;
notification.defaults |= Notification.DEFAULT_VIBRATE;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use the &lt;code&gt;setLatestEventInfo&lt;/code&gt; method to set another PendingIntent into the notification. The Activity in this intent will be called when the user clicks the notification. Again you can include a Uri into the intent to pass data to the Activity about which notification was clicked. Finally, be sure to use a unique id when calling the Notification managers notify method to actually fire the notification.&lt;/p&gt;
&lt;h2 id=&quot;Activity&quot; name=&quot;Activity&quot;&gt;Activity&lt;/h2&gt;
&lt;p&gt;When the user has clicked the notification is it probably safe to remove it. In the Activity which gets called by the notification, a call to the NotificationManagers cancel can be used to do this. The id which was used to fire the notification in the BroadcastReceiver will let the system know which notification to remove.&lt;/p&gt;
&lt;h2 id=&quot;Reloading&quot; name=&quot;Reloading&quot;&gt;Reloading&lt;/h2&gt;
&lt;p&gt;Android&#039;s Alarm Manager does not remember alarms when the device reboots. In order to restore the alarms you need to take these steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;In the Activity which sets the alarms, also save information about each alarm into a database.&lt;/li&gt;
&lt;li&gt;Create an additional BroadcastReceiver which gets called at boot-up to re-install the alarms.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Add the &lt;code&gt;android.permission.RECEIVE_BOOT_COMPLETED&lt;/code&gt; uses-permission and the following receiver to the manifest.xml&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    &amp;lt;receiver
        android:name=&quot;package.AlarmSetter&quot;
    &amp;gt;
        &amp;lt;intent-filter&amp;gt;
            &amp;lt;action
                android:name=&quot;android.intent.action.BOOT_COMPLETED&quot; /&amp;gt;
        &amp;lt;/intent-filter&amp;gt;
    &amp;lt;/receiver&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the onReceive method, read the database and re-install the alarms in the same way as was done at the top.&lt;/p&gt;
&lt;h2 id=&quot;Proximity+Alerts&quot; name=&quot;Proximity+Alerts&quot;&gt;Proximity Alerts&lt;/h2&gt;
&lt;p&gt;A similar mechanism can be used for proximity alerts. The LocationManager will fire an alert which is nearly identical to an alarm. The same mechanism to restore alerts can be used after rebooting the device.&lt;/p&gt;</content>
</entry>
<entry>
<title>DID vendors</title>
<link href="https://www.0ink.net/posts/2013/2013-09-13-did-vendors.html"></link>
<id>urn:uuid:0b48a579-578c-7a20-9dcc-3d6565de7540</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[So I have been researching DID vendors with limited success.  So far my
leading candidates are:



Vendor
...]]></summary>
<content type="html">&lt;p&gt;So I have been researching DID vendors with limited success.  So far my
leading candidates are:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Vendor&lt;/th&gt;
&lt;th&gt;Country&lt;/th&gt;
&lt;th&gt;Set-up fee&lt;/th&gt;
&lt;th&gt;Monthly fee&lt;/th&gt;
&lt;th&gt;Per-Minute&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sonetel&lt;/td&gt;
&lt;td&gt;NL&lt;/td&gt;
&lt;td&gt;EUR 1.40&lt;/td&gt;
&lt;td&gt;EUR 1.40&lt;/td&gt;
&lt;td&gt;EUR 0.01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sonetel&lt;/td&gt;
&lt;td&gt;Peru&lt;/td&gt;
&lt;td&gt;EUR 5.50&lt;/td&gt;
&lt;td&gt;EUR 5.50&lt;/td&gt;
&lt;td&gt;EUR 0.01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sonetel&lt;/td&gt;
&lt;td&gt;USA&lt;/td&gt;
&lt;td&gt;EUR 0.70&lt;/td&gt;
&lt;td&gt;EUR 0.70&lt;/td&gt;
&lt;td&gt;EUR 0.01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;twilio&lt;/td&gt;
&lt;td&gt;NL&lt;/td&gt;
&lt;td&gt;USD 1.00&lt;/td&gt;
&lt;td&gt;USD 0.01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;twilio&lt;/td&gt;
&lt;td&gt;Peru&lt;/td&gt;
&lt;td&gt;USD 5.00&lt;/td&gt;
&lt;td&gt;USD 0.01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;twilio&lt;/td&gt;
&lt;td&gt;USA&lt;/td&gt;
&lt;td&gt;USD 1.00&lt;/td&gt;
&lt;td&gt;USD 0.01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;callcentric&lt;/td&gt;
&lt;td&gt;USA&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;callcentric&lt;/td&gt;
&lt;td&gt;USA&lt;/td&gt;
&lt;td&gt;USD 3.95&lt;/td&gt;
&lt;td&gt;USD 1.95&lt;/td&gt;
&lt;td&gt;USD 0.015&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;callcentric&lt;/td&gt;
&lt;td&gt;NL&lt;/td&gt;
&lt;td&gt;USD 7.95&lt;/td&gt;
&lt;td&gt;USD 7.94&lt;/td&gt;
&lt;td&gt;USD 0.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;callcentric&lt;/td&gt;
&lt;td&gt;Peru&lt;/td&gt;
&lt;td&gt;USD 9.95&lt;/td&gt;
&lt;td&gt;USD11.95&lt;/td&gt;
&lt;td&gt;USD 0.00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Note that &lt;a href=&quot;http://sonetel.com&quot;&gt;Sonetel&lt;/a&gt; has a free trial number
available so it would probably be better for initial set-up. The
&lt;a href=&quot;http://www.callcentric.com/did/&quot;&gt;callcentric&lt;/a&gt; has free numbers
(limited area coverage). It also has interesting phone rates for
outgoing calls.&lt;/p&gt;</content>
</entry>
<entry>
<title>Parsing JSON in Shell scripts</title>
<link href="https://www.0ink.net/posts/2013/2013-09-06-parsing-json-in-shell-scripts.html"></link>
<id>urn:uuid:4b85e9c1-c2e5-361c-15a7-8c2bc0ff8180</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This can be simple by using jq.
This is a command line JSON processor.  Here are a couple of examples of what can be done:
$ cat json.txt

{
        "name": "Google",
...]]></summary>
<content type="html">&lt;p&gt;This can be simple by using &lt;a href=&quot;http://stedolan.github.io/jq/&quot;&gt;jq&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This is a command line JSON processor.  Here are a couple of examples of what can be done:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ cat json.txt

{
        &quot;name&quot;: &quot;Google&quot;,
        &quot;location&quot;:
                {
                        &quot;street&quot;: &quot;1600 Amphitheatre Parkway&quot;,
                        &quot;city&quot;: &quot;Mountain View&quot;,
                        &quot;state&quot;: &quot;California&quot;,
                        &quot;country&quot;: &quot;US&quot;
                },
        &quot;employees&quot;:
                [
                        {
                                &quot;name&quot;: &quot;Michael&quot;,
                                &quot;division&quot;: &quot;Engineering&quot;
                        },
                        {
                                &quot;name&quot;: &quot;Laura&quot;,
                                &quot;division&quot;: &quot;HR&quot;
                        },
                        {
                                &quot;name&quot;: &quot;Elise&quot;,
                                &quot;division&quot;: &quot;Marketing&quot;
                        }
                ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To parse a JSON object:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;jq &#039;.name&#039; &amp;lt; json.txt

&quot;Google&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To parse a nested JSON object:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ jq &#039;.location.city&#039; &amp;lt; json.txt

&quot;Mountain View&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To parse a JSON array:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ jq &#039;.employees[0].name&#039; &amp;lt; json.txt

&quot;Michael&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To extract specific fields from a JSON object:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ jq &#039;.location | {street, city}&#039; &amp;lt; json.txt

{
  &quot;city&quot;: &quot;Mountain View&quot;,
  &quot;street&quot;: &quot;1600 Amphitheatre Parkway&quot;
}
&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>Yealink W52P</title>
<link href="https://www.0ink.net/posts/2013/2013-08-28-yealink-w52p.html"></link>
<id>urn:uuid:02cd75de-5c0d-8e6d-d3c0-9bea19eac9aa</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Yealink W52P

So I was looking to replace my analog cordless phones mainly because I wanted to have a centralized way to maintain phonebooks. Right now I have two cordless phone that I have to manually enter phonebook entries on the two handsets independently.
Initially I was thinking of getting small/cheap Android tablet and load it with a SIP soft phone. Trying with a couple of tablets I had was not very successful. On one hand my network topology did not work very well, on the other hand, the integration of the SIP soft phone with the directory and the other phone functions did not work as well as I expected.
So when I came across the W52P, I was initially attracted to the low price. Grandstream had a cheaper phone, but it did not have remote phonebooks. After checking the documentation of the W52P, I confirmed that it did have a remote phonebook functionality. So bought it and tried it out.
As a phone itself, it is about the same as the analog phones that it was replacing. The voice quality was pretty good.
...]]></summary>
<content type="html">&lt;p&gt;&lt;a href=&quot;http://www.yealink.com/product_info.aspx?ProductsCateID=308&quot;&gt;Yealink W52P&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2013/W52PwebRpicture20X20CM-01590427430.jpg&quot; alt=&quot;phone&quot; /&gt;&lt;/p&gt;
&lt;p&gt;So I was looking to replace my analog cordless phones mainly because I wanted to have a centralized way to maintain phonebooks. Right now I have two cordless phone that I have to manually enter phonebook entries on the two handsets independently.&lt;/p&gt;
&lt;p&gt;Initially I was thinking of getting small/cheap Android tablet and load it with a SIP soft phone. Trying with a couple of tablets I had was not very successful. On one hand my network topology did not work very well, on the other hand, the integration of the SIP soft phone with the directory and the other phone functions did not work as well as I expected.&lt;/p&gt;
&lt;p&gt;So when I came across the W52P, I was initially attracted to the low price. Grandstream had a cheaper phone, but it did not have remote phonebooks. After checking the documentation of the W52P, I confirmed that it did have a remote phonebook functionality. So bought it and tried it out.&lt;/p&gt;
&lt;p&gt;As a phone itself, it is about the same as the analog phones that it was replacing. The voice quality was pretty good.&lt;/p&gt;
&lt;p&gt;Configuring the remote phone book was not as straight forward as I would have hoped. I was reusing the same phonebook script that I had used for my Grandstream phone. But I was getting &lt;code&gt;&quot;CONNECT ERROR&quot;&lt;/code&gt; when I tried to use the remote phone. This was not very useful trying to figure out what was wrong. Turns out, because I was using a dynamic script, the script was not setting the &lt;code&gt;Content-Length&lt;/code&gt; HTTP header. This apparently caused the phonebook not to download. Calculating the &lt;code&gt;Content-Length&lt;/code&gt; header and setting it made the system work like a charm.&lt;/p&gt;</content>
</entry>
<entry>
<title>Grandstream GXP1400</title>
<link href="https://www.0ink.net/posts/2013/2013-08-28-grandstream-gxp1400.html"></link>
<id>urn:uuid:6333cc84-73f0-5888-5c21-ccafa718dfe7</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Grandstream GXP1400

The other day I replaced an analog phone with a Grandstream GXP1400 IP phone. I think it is a great value phone. It is one of the cheapest I could find yet supports all the features I was looking.
Specifically I wanted a IP phone that could:

Have a remote phone directory
...]]></summary>
<content type="html">&lt;p&gt;&lt;a href=&quot;http://www.grandstream.com/products/ip-voice-telephony/enterprise-ip-phones/product/gxp1400/1405&quot;&gt;Grandstream GXP1400&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2013/gxp1400.jpg&quot; alt=&quot;gs&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The other day I replaced an analog phone with a Grandstream GXP1400 IP phone. I think it is a great value phone. It is one of the cheapest I could find yet supports all the features I was looking.&lt;/p&gt;
&lt;p&gt;Specifically I wanted a IP phone that could:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Have a remote phone directory&lt;/li&gt;
&lt;li&gt;Speaker phone&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Setting it up was simple. Creating an account on Asterisk and providing the details for it to the phone. Creating a phone directory was also quite simple. I wrote a small PHP script to manage that.&lt;/p&gt;</content>
</entry>
<entry>
<title>Backing up GMail</title>
<link href="https://www.0ink.net/posts/2013/2013-08-28-backing-up-gmail.html"></link>
<id>urn:uuid:3d522e6e-bd7d-acdf-2195-bab4d1ae36e4</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[The other day I found Gmvault.
Gmvault is an open source Gmail backup software written in Python.
This article provides a good overview on how it works (found it better than the Gmvault documentation):

How to back up and restore Gmail account on Linux

...]]></summary>
<content type="html">&lt;p&gt;The other day I found &lt;a href=&quot;http://gmvault.org/index.html&quot;&gt;Gmvault&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Gmvault is an open source Gmail backup software written in Python.&lt;/p&gt;
&lt;p&gt;This article provides a good overview on how it works (found it better than the Gmvault documentation):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://xmodulo.com/2013/08/how-to-back-up-and-restore-gmail-account-on-linux.html&quot;&gt;How to back up and restore Gmail account on Linux&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It uses IMAP to connect to Gmail and also stores files in &lt;code&gt;.eml&lt;/code&gt; (plain text) formatted fails.  It has a converter to export to mbox and Maildir formats.&lt;/p&gt;
&lt;p&gt;Probably would be good for archiving and using &lt;a href=&quot;http://freecode.com/projects/udmsearch&quot;&gt;mnoGoSearch&lt;/a&gt; for search front end.&lt;/p&gt;</content>
</entry>
<entry>
<title>BOX.com promotions</title>
<link href="https://www.0ink.net/posts/2013/2013-08-26-box-com-promotions.html"></link>
<id>urn:uuid:d66bd93d-e328-8342-1a17-3bf4a8f570af</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This is a good link to keep an eye on: box.com promotions
...]]></summary>
<content type="html">&lt;p&gt;This is a good link to keep an eye on: &lt;a href=&quot;https://support.box.com/entries/22057282-box-promotions-faq&quot;&gt;box.com promotions&lt;/a&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>Alternative to DynDNS</title>
<link href="https://www.0ink.net/posts/2013/2013-08-23-alternative-to-dyndns.html"></link>
<id>urn:uuid:b4107807-b803-22d8-b0fe-cecf115e17d1</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[linuxaria blog article This article has a script how to use Dynamic DNS on afraid.org.
...]]></summary>
<content type="html">&lt;p&gt;&lt;a href=&quot;http://linuxaria.com/howto/dynamic-dns-with-bash-afraid-org&quot;&gt;linuxaria blog article&lt;/a&gt; This article has a script how to use Dynamic DNS on &lt;a href=&quot;http://freedns.afraid.org/&quot;&gt;afraid.org&lt;/a&gt;.&lt;/p&gt;</content>
</entry>
<entry>
<title>assist</title>
<link href="https://www.0ink.net/posts/2013/2013-08-22-assist.html"></link>
<id>urn:uuid:a642dfc3-e960-555f-6993-7573fc80f64f</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Assist is my archlinux scripted installation script.

https://github.com/alejandroliu/assist

By default it gives you a menu driven archlinux installation with supposedly sensible defaults.
It has command line hooks so that you can perform automated installs using bash scripts to customize it.
...]]></summary>
<content type="html">&lt;p&gt;Assist is my &lt;a href=&quot;http://www.archlinux.org/&quot;&gt;archlinux&lt;/a&gt; scripted installation script.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alejandroliu/assist&quot;&gt;https://github.com/alejandroliu/assist&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By default it gives you a menu driven &lt;a href=&quot;http://www.archlinux.org/&quot;&gt;archlinux&lt;/a&gt; installation with supposedly &lt;em&gt;sensible&lt;/em&gt; defaults.&lt;/p&gt;
&lt;p&gt;It has command line hooks so that you can perform automated installs using bash scripts to customize it.&lt;/p&gt;
&lt;p&gt;It can be deployed from the CDROM by downloading and executing Assist directly from the Internet or by injecting it into the init ramdisk for deployment either from PXE or a custom boot CDROM.&lt;/p&gt;</content>
</entry>
<entry>
<title>SSH Tricks</title>
<link href="https://www.0ink.net/posts/2013/2013-08-21-ssh-tricks.html"></link>
<id>urn:uuid:43680883-ee34-4f91-6851-c1893c53b8dd</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[A bunch of stupid SSH tricks that can be useful somehow, somewhere...
Forcing either IPv4 or IPv6
This is for the scenario that you know which specific protocol works
to reach a particular host. Usually good to eliminate the delay
for SSH to figure out to switch IP protocols. For IPv4:
ssh -4 user@hostname.com
...]]></summary>
<content type="html">&lt;p&gt;A bunch of stupid SSH tricks that can be useful somehow, somewhere...&lt;/p&gt;
&lt;h2&gt;Forcing either IPv4 or IPv6&lt;/h2&gt;
&lt;p&gt;This is for the scenario that you know which specific protocol works&lt;br /&gt;
to reach a particular host. Usually good to eliminate the delay&lt;br /&gt;
for SSH to figure out to switch IP protocols. For IPv4:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssh -4 user@hostname.com&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For IPv6&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssh -6 user@hostname.com&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Reuse a SSH connection&lt;/h2&gt;
&lt;p&gt;Rather than start a new TCP connection to a remote host, simply
multiplex over an existing connection: Add to your &lt;code&gt;~/.ssh/config&lt;/code&gt; the
following lines:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Host *
    ControlMaster auto
    ControlPath /tmp/%r@%h:%p
    ControlPersist 4h
# Another option for Control Path
    ControlPath ~/.ssh/%r@%h:%p&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Enable compression&lt;/h2&gt;
&lt;p&gt;Use the &lt;code&gt;-C&lt;/code&gt; option. Or in the config file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Compression yes&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Using cheaper cyphers&lt;/h2&gt;
&lt;p&gt;Using less computation-heavy ciphers in SSH, so that less time is spent
during encryption/decryption. The default &lt;strong&gt;AES&lt;/strong&gt; cipher used by
OpenSSH is known to be slow. An independent study shows that
&lt;strong&gt;arcfour&lt;/strong&gt; and &lt;strong&gt;blowfish&lt;/strong&gt; ciphers are faster than &lt;strong&gt;AES&lt;/strong&gt;.
&lt;strong&gt;blowfish&lt;/strong&gt; is a fast block cipher which is also very secure.
Meanwhile, &lt;strong&gt;arcfour&lt;/strong&gt; stream cipher is known to have vulnerabilities.
So use caution when using &lt;strong&gt;arcfour&lt;/strong&gt;. Use the &lt;code&gt;-c blowfish-cbc,arcfour&lt;/code&gt;
option or in the config file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Ciphers blowfish-cbc,arcfour&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Improve Session Persistence&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;ServerAliveInterval 60
ServerAliveCountMax 10
TCPKeepAlive no&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Counterintuitively, setting this results in fewer disconnections from
your host, as transient TCP problems can self-repair in ways that fly
below SSH&#039;s radar. You may not want to apply this to scripts that work
via SSH, as &amp;quot;parts of the SSH tunnel going non-responsive&amp;quot; may work in
ways you neither want nor expect!&lt;/p&gt;</content>
</entry>
<entry>
<title>Running Windows on Linux for Free</title>
<link href="https://www.0ink.net/posts/2013/2013-08-21-running-windows-on-linux-for-free.html"></link>
<id>urn:uuid:f31fa1a7-bd5e-7679-1b44-c55766d24fd8</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Microsoft is now making available Windows VM image for testing Internet Explorer for free. You can find them at: Modern IE testing Currently the following versions are available:

Windows XP Professional SP3 + IE 6 or 8
Windows Vista + IE 7
Windows 7 + IE 8, 9, 10 or 11
Windows 8 + IE 11
...]]></summary>
<content type="html">&lt;p&gt;Microsoft is now making available Windows VM image for testing Internet Explorer for free. You can find them at: &lt;a href=&quot;http://www.modern.ie/en-us&quot;&gt;Modern IE testing&lt;/a&gt; Currently the following versions are available:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Windows XP Professional SP3 + IE 6 or 8&lt;/li&gt;
&lt;li&gt;Windows Vista + IE 7&lt;/li&gt;
&lt;li&gt;Windows 7 + IE 8, 9, 10 or 11&lt;/li&gt;
&lt;li&gt;Windows 8 + IE 11&lt;/li&gt;
&lt;li&gt;Windows 8.1 Preview + IE 11&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If Administrator access is needed the password is:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Passw0rd!
&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>Remote VirtualBox</title>
<link href="https://www.0ink.net/posts/2013/2013-08-21-remotebox.html"></link>
<id>urn:uuid:0c0fea6c-56f5-3a62-f120-05d6e2990bf0</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[RemoteBox is a Remote VirtualBox UI. It is similar phpVirtualBox in that allows to manage VirtualBox remotely (on a potentially headless server). They differ in their requirements:

RemoteBox does not require much on the server, but you need to install it on the client.
phpVirtualBox only requires a browser and rdp viewer on the client, but requires a web server with PHP support on the server.

...]]></summary>
<content type="html">&lt;p&gt;&lt;a href=&quot;http://knobgoblin.org.uk/&quot; title=&quot;RemoteBox&quot;&gt;RemoteBox&lt;/a&gt; is a Remote VirtualBox UI. It is similar &lt;a href=&quot;http://sourceforge.net/projects/phpvirtualbox/&quot; title=&quot;phpVirtualBox&quot;&gt;phpVirtualBox&lt;/a&gt; in that allows to manage VirtualBox remotely (on a potentially headless server). They differ in their requirements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://knobgoblin.org.uk/&quot; title=&quot;RemoteBox&quot;&gt;RemoteBox&lt;/a&gt; does not require much on the server, but you need to install it on the client.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://sourceforge.net/projects/phpvirtualbox/&quot; title=&quot;phpVirtualBox&quot;&gt;phpVirtualBox&lt;/a&gt; only requires a browser and rdp viewer on the client, but requires a web server with PHP support on the server.&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>IPv6 testing</title>
<link href="https://www.0ink.net/posts/2013/2013-07-04-ipv6-testing.html"></link>
<id>urn:uuid:d6bdb8ad-f8ff-3384-c1fe-7e5e5a164a79</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[When trying to get on-to the IPv6 Internet, here are a couple of links to do diagnostics:

http://www.subnetonline.com/pages/ipv6-network-tools/online-ipv6-ping.php
This actually contain generic network tools.
http://ds.testmyipv6.com/
Confirm if your browser is connecting through IPv6
...]]></summary>
<content type="html">&lt;p&gt;When trying to get on-to the IPv6 Internet, here are a couple of links to do diagnostics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.subnetonline.com/pages/ipv6-network-tools/online-ipv6-ping.php&quot;&gt;http://www.subnetonline.com/pages/ipv6-network-tools/online-ipv6-ping.php&lt;/a&gt;&lt;br /&gt;
This actually contain generic network tools.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://ds.testmyipv6.com/&quot;&gt;http://ds.testmyipv6.com/&lt;/a&gt;&lt;br /&gt;
Confirm if your browser is connecting through IPv6&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://test-ipv6.com/&quot;&gt;http://test-ipv6.com/&lt;/a&gt;&lt;br /&gt;
Check also DNS&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>PingTool.org</title>
<link href="https://www.0ink.net/posts/2013/2013-07-03-pingtool-org.html"></link>
<id>urn:uuid:67f32ed0-c8db-c550-dc04-a2211278d0f2</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Another short and sweet. This web site provides a number of on-line
tools. Useful for diagnosing problems when setting a home server.
http://pingtool.org/
...]]></summary>
<content type="html">&lt;p&gt;Another short and sweet. This web site provides a number of on-line
tools. Useful for diagnosing problems when setting a home server.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://pingtool.org/&quot;&gt;http://pingtool.org/&lt;/a&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>Web Backups</title>
<link href="https://www.0ink.net/posts/2013/2013-07-02-web-backups.html"></link>
<id>urn:uuid:9e4288cc-03b8-915a-e146-ead50b58fb76</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[
As usual with any IT system backups are important. This does not change when using a free shared hosting provider. Because it is free, one would argue it is even more important.
For my wordpress web site I used something called cli-exporter. It let's you create &quot;Wordpress&quot; export files from the command line so it can be run from cron. This is important because backups have to be automated.
In addition to that, I copy the backup files to an off-site location. I do this by copying files using WebDAV to a storage provider. I did this by writing a simple script and using the PHP library SabreDAV which makes writing DAV clients quite easy.
I myself don't mind using other people's Open Source code to do something. I was actually surprised that I was not able to find something that meet my criteria. However, thanks to the power of open source I was able to find something that fit the bill exactly.
To make things more interesting, because I wanted to keep backup files as compressed Zip archives, my backup scripts did not work in one of the web hosts that I was using. They did not have the zip extensions enabled. This is surprising considering is quite standard. Luckily I was able to find a pure PHP library pclzip.
...]]></summary>
<content type="html">&lt;p&gt;&lt;img src=&quot;/images/2013/bb-images.jpg&quot; alt=&quot;cfback&quot; /&gt;&lt;/p&gt;
&lt;p&gt;As usual with any IT system backups are important. This does not change when using a free shared hosting provider. Because it is free, one would argue it is even more important.&lt;/p&gt;
&lt;p&gt;For my wordpress web site I used something called &lt;a href=&quot;https://github.com/Automattic/WordPress-CLI-Exporter&quot;&gt;cli-exporter&lt;/a&gt;. It let&#039;s you create &amp;quot;Wordpress&amp;quot; export files from the command line so it can be run from &lt;code&gt;cron&lt;/code&gt;. This is important because backups &lt;em&gt;have&lt;/em&gt; to be automated.&lt;/p&gt;
&lt;p&gt;In addition to that, I copy the backup files to an off-site location. I do this by copying files using WebDAV to a storage provider. I did this by writing a simple script and using the PHP library &lt;a href=&quot;http://code.google.com/p/sabredav/wiki/WebDAVClient&quot;&gt;SabreDAV&lt;/a&gt; which makes writing DAV clients quite easy.&lt;/p&gt;
&lt;p&gt;I myself don&#039;t mind using other people&#039;s Open Source code to do something. I was actually surprised that I was not able to find something that meet my criteria. However, thanks to the power of open source I was able to find something that fit the bill exactly.&lt;/p&gt;
&lt;p&gt;To make things more interesting, because I wanted to keep backup files as compressed Zip archives, my backup scripts did not work in one of the web hosts that I was using. They did not have the &lt;code&gt;zip&lt;/code&gt; extensions enabled. This is surprising considering is quite standard. Luckily I was able to find a pure PHP library &lt;a href=&quot;http://www.phpconcept.net/pclzip/&quot;&gt;pclzip&lt;/a&gt;.&lt;/p&gt;</content>
</entry>
<entry>
<title>Mini-Howto: Setup proxy on Ubuntu</title>
<link href="https://www.0ink.net/posts/2013/2013-07-02-mini-howto-setup-proxy-on-ubuntu.html"></link>
<id>urn:uuid:7e044ab8-dfaa-dcc8-1eff-5121ce79205c</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[A quick and dirty mini-howto to setup a proxy on Ubuntu.
This is meant mostly for doing quick setup of a proxy
on a cloud environment.


Install Squid with the following command at the Linux command prompt:
...]]></summary>
<content type="html">&lt;p&gt;A quick and dirty mini-howto to setup a proxy on Ubuntu.
This is meant mostly for doing quick setup of a proxy
on a cloud environment.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2013/logo-ubuntu_su-orange-hex.jpg&quot; alt=&quot;logo-ubuntu_su-orange-hex&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Install Squid with the following command at the Linux command prompt:
&lt;code&gt;sudo apt-get install squid&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Edit the Squid config file in &lt;code&gt;/etc/squid&lt;/code&gt; adding these lines:
&lt;code&gt;http_access allow local_net&lt;/code&gt;
&lt;code&gt;acl local_net src 10.10.0.0/255.255.0.0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Save the file, exit the editor and restart Squid. You are now ready to configure your browser to use the proxy server.&lt;/li&gt;
&lt;li&gt;Click &amp;quot;Tools,&amp;quot; &amp;quot;Options,&amp;quot; &amp;quot;Advanced,&amp;quot; &amp;quot;Network&amp;quot; and &amp;quot;Settings&amp;quot; in Firefox, which is the normal Ubuntu Linux browser. Select &amp;quot;Manual Proxy Configuration,&amp;quot; enter the IP address of your proxy server, enter port 3128 in the Port field and then click &amp;quot;OK.&amp;quot;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;References:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://science.opposingviews.com/set-up-secure-proxy-server-ubuntu-linux-23184.html&quot;&gt;http://science.opposingviews.com/set-up-secure-proxy-server-ubuntu-linux-23184.html&lt;/a&gt; by Alan Hughes&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Using CloudFlare</title>
<link href="https://www.0ink.net/posts/2013/2013-07-01-using-cloudflare.html"></link>
<id>urn:uuid:38397aac-3e8a-0928-59cd-1f1a0ac8373e</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[So I have signed up 0ink.net to use the CloudFlare service.

CloudFlare is a reverse proxy service that is supposed to speed up and improve web server security.
This is done by:

globally distributed reverse proxy cache network
...]]></summary>
<content type="html">&lt;p&gt;So I have signed up &lt;code&gt;0ink.net&lt;/code&gt; to use the &lt;a href=&quot;http://www.cloudflare.com&quot; title=&quot;CloudFlare&quot;&gt;CloudFlare&lt;/a&gt; service.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2013/cf-logo-v-rgb.png&quot; alt=&quot;CFLogo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.cloudflare.com&quot; title=&quot;CloudFlare&quot;&gt;CloudFlare&lt;/a&gt; is a reverse proxy service that is supposed to speed up and improve web server security.&lt;/p&gt;
&lt;p&gt;This is done by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;globally distributed reverse proxy cache &lt;a href=&quot;http://www.cloudflare.com/system-status.html&quot; title=&quot;Cloudflare status&quot;&gt;network&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;filters incoming request for attacks&lt;/li&gt;
&lt;li&gt;optimize content (i.e. compressing, removing redundnat text, etc).&lt;/li&gt;
&lt;li&gt;improving retrieving of web pages that have multiple components.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For it to work they need to take over your DNS service. That means that your DNS records resolve to &lt;a href=&quot;http://www.cloudflare.com&quot; title=&quot;CloudFlare&quot;&gt;CloudFlare&lt;/a&gt; servers. So when editing your DNS records, the &lt;a href=&quot;http://www.cloudflare.com&quot; title=&quot;CloudFlare&quot;&gt;CloudFlare&lt;/a&gt; DNS editor has an extra settings that allows you to control if that DNS entry would use the &lt;a href=&quot;http://www.cloudflare.com&quot; title=&quot;CloudFlare&quot;&gt;CloudFlare&lt;/a&gt; network or not.&lt;/p&gt;
&lt;p&gt;So if you want to be able to access your web server (i.e. www) for &lt;code&gt;ftp&lt;/code&gt; or &lt;code&gt;ssh&lt;/code&gt;, then you need
to create an additinional CNAME record that points to the web server but set to by-pass the &lt;a href=&quot;http://www.cloudflare.com&quot; title=&quot;CloudFlare&quot;&gt;CloudFlare&lt;/a&gt; network.&lt;/p&gt;
&lt;p&gt;Some tips on what to do after install cloudflare can be found &lt;a href=&quot;http://blog.cloudflare.com/top-tips-after-installing-cloudflare&quot; title=&quot;Tips on using Cloudflare&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This is handy command to test if your web-server is having problems but not &lt;a href=&quot;http://www.cloudflare.com&quot; title=&quot;CloudFlare&quot;&gt;CloudFlare&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; curl -v -A firefox/4.0 -H &#039;Host: yourdomain.com&#039; YourServerIP&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>Askozia Desktop Appliance</title>
<link href="https://www.0ink.net/posts/2013/2013-07-01-askozia-desktop-appliance.html"></link>
<id>urn:uuid:bb340183-7be3-3ed6-5b87-ed10d9e671bf</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[
So last weekend finally had some time to work with a Askozia Desktop Appliance.
It actually arrived much earlier but without a Power Supply. Initially I though, &quot;this is strange; I didn't know this supported PoE&quot;. (Power Over Ethernet). It turns out it didn't and there was a shipping mistake. After contacting the vendor, they sent me the required Power Supply.
Overall I think the product is quite nice. It has a very nice User Interface that is quite easy to use. Simple configurations are indeed very easy to set-up.
My feeling is that, as with any GUI, it usually trades user-friendly with expressiveness. So while I could configure most of the things I wanted from the UI, it did not support my home network topology fully.
Initially, I had a DMZ vs Home-LAN configuration, with the Askozia box in the DMZ. Because the separation between the DMZ and the Home-LAN was through the router, it considered all the IP phones (in the Home-LAN) on the other side of the NAT, so things did not work properly.
...]]></summary>
<content type="html">&lt;p&gt;&lt;img src=&quot;/images/2013/askozia_logo.png&quot; alt=&quot;Askozia Logo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;So last weekend finally had some time to work with a &lt;a href=&quot;http://askozia.com/&quot; title=&quot;Askozia PBX&quot;&gt;Askozia&lt;/a&gt; Desktop Appliance.&lt;/p&gt;
&lt;p&gt;It actually arrived much earlier but without a Power Supply. Initially I though, &amp;quot;this is strange; I didn&#039;t know this supported PoE&amp;quot;. (Power Over Ethernet). It turns out it didn&#039;t and there was a shipping mistake. After contacting the vendor, they sent me the required Power Supply.&lt;/p&gt;
&lt;p&gt;Overall I think the product is quite nice. It has a very nice User Interface that is quite easy to use. Simple configurations are indeed very easy to set-up.&lt;/p&gt;
&lt;p&gt;My feeling is that, as with any GUI, it usually trades user-friendly with expressiveness. So while I could configure most of the things I wanted from the UI, it did not support my home network topology fully.&lt;/p&gt;
&lt;p&gt;Initially, I had a DMZ vs Home-LAN configuration, with the Askozia box in the DMZ. Because the separation between the DMZ and the Home-LAN was through the router, it considered all the IP phones (in the Home-LAN) on the other side of the NAT, so things did not work properly.&lt;/p&gt;
&lt;p&gt;After moving the appliance to the same LAN as the IP phones things started working properly. Normally under Asterisk this would be solved through the &amp;quot;localnets&amp;quot; settings. The UI obviously did not expose this setting.&lt;/p&gt;
&lt;p&gt;Next time I will try to use the &lt;a href=&quot;http://askozia.com/handbook/index.php?title=Help_for_Integrators&quot; title=&quot;Askozia Handbook: Integrator Panel&quot;&gt;Integrator Panel&lt;/a&gt; as it is supposed to expose the Asterisk configuration files directly.&lt;/p&gt;</content>
</entry>
<entry>
<title>CipherUSB</title>
<link href="https://www.0ink.net/posts/2013/2013-06-24-cipherusb.html"></link>
<id>urn:uuid:bdc7fe3b-8460-8770-5226-941932184e24</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This is an interesting concept. Essentially is an Encryption dongle
that encrypts stuff between your PC and your USB mass storage device.
Addonics Product: CipherUSB.
...]]></summary>
<content type="html">&lt;p&gt;This is an interesting concept. Essentially is an Encryption dongle
that encrypts stuff between your PC and your USB mass storage device.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.addonics.com/products/cipherusb.php&quot;&gt;Addonics Product: CipherUSB&lt;/a&gt;.&lt;/p&gt;</content>
</entry>
<entry>
<title>Upgrading pacman config files</title>
<link href="https://www.0ink.net/posts/2013/2013-06-06-3-way-merging.html"></link>
<id>urn:uuid:ebb811df-0c59-bafd-0fbd-65bb68af962f</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[So when upgrading software packages sometimes you need to merge changes. My recipe in archlinux is as follows:

Look for *.pacnew files.
Retrieve the original version (from /var/cache/pacman) from the old source package.
Use a 3 way merge tool between old version, current file and the pacnew file.

...]]></summary>
<content type="html">&lt;p&gt;So when upgrading software packages sometimes you need to merge changes. My recipe in &lt;strong&gt;archlinux&lt;/strong&gt; is as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Look for *&lt;strong&gt;.pacnew&lt;/strong&gt; files.&lt;/li&gt;
&lt;li&gt;Retrieve the original version (from /var/cache/pacman) from the old source package.&lt;/li&gt;
&lt;li&gt;Use a 3 way merge tool between old version, current file and the pacnew file.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;These are my options for merging:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;diff3 -m : Merges the changes into a single file (Use -m option)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://diffuse.sourceforge.net/&quot; title=&quot;diffuse&quot;&gt;diffuse&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://meldmerge.org/&quot; title=&quot;meld merge&quot;&gt;meld&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>libmspack</title>
<link href="https://www.0ink.net/posts/2013/2013-06-05-libmspack.html"></link>
<id>urn:uuid:417921e8-fa73-6e77-dc97-b97f050bc093</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Recently I found this Open Source project. Apparently it recently
gained support to unpack Exchange Offline Address Book files. What
I don't know is after you unpack it, how would you use such a file.
Intriguing but apparently falls a little bit short. Probably would need
to try it out for myself to see how it works.
libmspack.
...]]></summary>
<content type="html">&lt;p&gt;Recently I found this Open Source project. Apparently it recently
gained support to unpack &lt;strong&gt;Exchange Offline Address Book&lt;/strong&gt; files. What
I don&#039;t know is after you unpack it, how would you use such a file.
Intriguing but apparently falls a little bit short. Probably would need
to try it out for myself to see how it works.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.cabextract.org.uk/libmspack/&quot;&gt;libmspack&lt;/a&gt;.&lt;/p&gt;</content>
</entry>
<entry>
<title>atratus project</title>
<link href="https://www.0ink.net/posts/2013/2013-06-05-atratus-project.html"></link>
<id>urn:uuid:261d494d-911e-ac38-4004-211973e9a2c9</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[The other day I came across this project. Looks an interesting idea. It is a project that lets you run unmodified Linux binaries on Windows. It is more similar to WINE than to for example coLinux. While I conceptually I understand how it would work at a low level, I am curious how it works with dynamically link executables. This is something I would like to test out when I have time.
...]]></summary>
<content type="html">&lt;p&gt;The other day I came across this &lt;a href=&quot;http://atratus.org/&quot; title=&quot;Atratus project&quot;&gt;project&lt;/a&gt;. Looks an interesting idea. It is a project that lets you run unmodified Linux binaries on Windows. It is more similar to WINE than to for example coLinux. While I conceptually I understand how it would work at a low level, I am curious how it works with dynamically link executables. This is something I would like to test out when I have time.&lt;/p&gt;</content>
</entry>
<entry>
<title>Media Tips</title>
<link href="https://www.0ink.net/posts/2013/2013-06-02-video-tips.html"></link>
<id>urn:uuid:9a9e12b6-c345-d2ef-e56e-6f146a992afb</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This is an article about different media (and more specifically)
video files can be manipulated.
This is just for historical purposes as now almost everything can be
done using ffmpeg and the right options.

libmp4v2 contains:
...]]></summary>
<content type="html">&lt;p&gt;This is an article about different media (and more specifically)
video files can be manipulated.&lt;/p&gt;
&lt;p&gt;This is just for historical purposes as now almost everything can be
done using &lt;code&gt;ffmpeg&lt;/code&gt; and the right options.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://code.google.com/p/mp4v2/&quot;&gt;libmp4v2&lt;/a&gt; contains:
&lt;ul&gt;
&lt;li&gt;mp4art - to extract a picture (or coverart from mp4)&lt;/li&gt;
&lt;li&gt;mp4info - to get meta data from mp4 streams&lt;/li&gt;
&lt;li&gt;mp4tags - to set metadata and picture.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;qt-fastload to move index to the front and making mp4 streamable&lt;/li&gt;
&lt;li&gt;When encoding:
&lt;ul&gt;
&lt;li&gt;Change max GOP or IDR to around 5 seconds.&lt;/li&gt;
&lt;li&gt;2-pass avg bitrate: 800 or even 500...&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Concatenating+files%3A&quot; name=&quot;Concatenating+files%3A&quot;&gt;Concatenating files:&lt;/h2&gt;
&lt;h3 id=&quot;ffmpeg&quot; name=&quot;ffmpeg&quot;&gt;ffmpeg&lt;/h3&gt;
&lt;p&gt;ffmpeg has a feature concat, like&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ffmpeg -i concat:&quot;video1.ts|video2.ts&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There is also a &amp;quot;concat&amp;quot; video filter that may be useful. See
&lt;a href=&quot;http://ffmpeg.org/trac/ffmpeg/wiki/How%20to%20concatenate%20%28join,%20merge%29%20media%20files&quot;&gt;http://ffmpeg.org/trac/ffmpeg/wiki/How%20to%20concatenate%20%28join,%20merge%29%20media%20files&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;gpac&quot; name=&quot;gpac&quot;&gt;gpac&lt;/h3&gt;
&lt;p&gt;An alternative is &lt;a href=&quot;http://gpac.wp.mines-telecom.fr/&quot;&gt;gpac&lt;/a&gt;. One command
it includes is MP4Box to concatenate MP4s&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mp4box -cat sbd0.mp4 -cat sbd1.mp4 -new sbd.mp4
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;AviDemux&quot; name=&quot;AviDemux&quot;&gt;AviDemux&lt;/h3&gt;
&lt;p&gt;Of course the avidemux GUI can append files.&lt;/p&gt;
&lt;h3 id=&quot;Final+notes&quot; name=&quot;Final+notes&quot;&gt;Final notes&lt;/h3&gt;
&lt;p&gt;So far I have not been able to create a reliable media concat recipe.&lt;/p&gt;
&lt;h2 id=&quot;Media+Gain&quot; name=&quot;Media+Gain&quot;&gt;Media Gain&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://mp3gain.sourceforge.net/&quot;&gt;mp3gain&lt;/a&gt; can be used to normalize
volume levels (without re-encoding). Accomplish this by using
&lt;a href=&quot;http://en.wikipedia.org/wiki/ReplayGain&quot;&gt;ReplayGain&lt;/a&gt; that needs to be
supported by player. (XBMC claims to supports this).&lt;/p&gt;</content>
</entry>
<entry>
<title>Diskless Archlinux</title>
<link href="https://www.0ink.net/posts/2013/2013-06-01-diskless-archlinux.html"></link>
<id>urn:uuid:be1821db-5b05-76af-1715-cebe2700806d</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[I am still to test this recipe
Server Configuration
First of all, we must install the following components:

A DHCP server to assign IP addresses to our diskless nodes.
A TFTP server to transfer the boot image (a requirement of all PXE option roms).
...]]></summary>
<content type="html">&lt;p&gt;&lt;em&gt;I am still to test this recipe&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;Server+Configuration&quot; name=&quot;Server+Configuration&quot;&gt;Server Configuration&lt;/h2&gt;
&lt;p&gt;First of all, we must install the following components:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A DHCP server to assign IP addresses to our diskless nodes.&lt;/li&gt;
&lt;li&gt;A TFTP server to transfer the boot image (a requirement of all PXE option roms).&lt;/li&gt;
&lt;li&gt;A form of network storage (NFS or NBD) to export the Arch installation to the diskless node.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note: dnsmasq is capable of simultaneously acting as both DHCP and TFTP server.&lt;/p&gt;
&lt;h3 id=&quot;Network+storage&quot; name=&quot;Network+storage&quot;&gt;Network storage&lt;/h3&gt;
&lt;p&gt;The primary difference between using NFS and NBD is while with both
you can in fact have multiple clients using the same installation,
with NBD (by the nature of manipulating a filesystem directly) you&#039;ll
need to use the copyonwrite mode to do so, which ends up discarding
all writes on client disconnect. In some situations however, this might
be highly desirable. Install nfs-utils on the server.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# pacman -Syu nfs-utils
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;NFSv4&quot; name=&quot;NFSv4&quot;&gt;NFSv4&lt;/h3&gt;
&lt;p&gt;You&#039;ll need to add the root of your arch installation to your NFS exports:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# vim /etc/exports
/srv/arch *(rw,fsid=0,no_root_squash,no_subtree_check)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, start NFS.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# systemctl start rpc-idmapd.service rpc-mountd.service
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;NFSv3&quot; name=&quot;NFSv3&quot;&gt;NFSv3&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# vim /etc/exports
/srv/arch *(rw,no_root_squash,no_subtree_check,sync)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, start NFSv3.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# systemctl start rpc-mountd.service rpc-statd.service
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note: If you&#039;re not worried about data loss in the event of network
and/or server failure, replace sync with async--additional options
can be found in the NFS article.&lt;/p&gt;
&lt;h3 id=&quot;NBD&quot; name=&quot;NBD&quot;&gt;NBD&lt;/h3&gt;
&lt;p&gt;Install nbd .&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# pacman -Syu nbd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Configure nbd.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# vim /etc/nbd-server/config
[generic]
    user = nbd
    group = nbd
[arch]
    exportname = /srv/arch.img
    copyonwrite = false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note: Set copyonwrite to true if you want to have multiple clients
using the same NBD share simultaneously; refer to man 5 nbd-server for
more details. Start nbd.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# systemctl start nbd.service
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Client+installation&quot; name=&quot;Client+installation&quot;&gt;Client installation&lt;/h2&gt;
&lt;p&gt;Next we will create a full Arch Linux installation in a subdirectory on
the server. During boot, the diskless client will get an IP address from
the DHCP server, then boot from the host using PXE and mount this
installation as its root.&lt;/p&gt;
&lt;h3 id=&quot;Directory+setup&quot; name=&quot;Directory+setup&quot;&gt;Directory setup&lt;/h3&gt;
&lt;h4 id=&quot;NBD&quot; name=&quot;NBD&quot;&gt;NBD&lt;/h4&gt;
&lt;p&gt;Create a sparse file of at least 1 gigabyte, and create a btrfs
filesystem on it (you can of course also use a real block device or
LVM if you so desire).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# truncate -s 1G /srv/arch.img
# mkfs.btrfs /srv/arch.img
# export root=/srv/arch
# mkdir -p &quot;$root&quot;
# mount -o loop,discard,compress=lzo /srv/arch.img &quot;$root&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note: Creating a separate filesystem is required for NBD but optional
for NFS and can be skipped/ignored.&lt;/p&gt;
&lt;h3 id=&quot;Bootstrapping+installation&quot; name=&quot;Bootstrapping+installation&quot;&gt;Bootstrapping installation&lt;/h3&gt;
&lt;p&gt;Install devtools and arch-install-scripts , and run mkarchroot.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# pacman -Syu devtools arch-install-scripts
# mkarchroot -f &quot;$root&quot; base mkinitcpio-nfs-utils nfs-utils
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note: In all cases mkinitcpio-nfs-utils is still required--ipconfig used
in early-boot is provided only by the latter. Now the initramfs needs to
be constructed. The shortest configuration, &lt;code&gt;#NFSv3&lt;/code&gt;, is presented as a
&amp;quot;base&amp;quot; upon which all subsequent sections modify as-needed.&lt;/p&gt;
&lt;h4 id=&quot;NFSv3&quot; name=&quot;NFSv3&quot;&gt;NFSv3&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;# vim &quot;$root/etc/mkinitcpio.conf&quot;
MODULES=&quot;nfsv3&quot;
HOOKS=&quot;base udev autodetect net filesystems&quot;
BINARIES=&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note: You&#039;ll also need to add the appropriate module for your ethernet
controller to the MODULES array. The initramfs now needs to be rebuilt;
the easiest way to do this is via arch-chroot .&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# arch-chroot &quot;$root&quot; /bin/bash
(chroot) # mkinitcpio -p linux
(chroot) # exit
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;NFSv4&quot; name=&quot;NFSv4&quot;&gt;NFSv4&lt;/h4&gt;
&lt;p&gt;Trivial modifications to the net hook are required in order for NFSv4
mounting to work (not supported by nfsmount--the default for the net hook).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# sed s/nfsmount/mount.nfs4/ &quot;$root/usr/lib/initcpio/hooks/net&quot; | tee &quot;$root/usr/lib/initcpio/hooks/net_nfs4&quot;
# cp &quot;$root/usr/lib/initcpio/install/{net,net_nfs4}&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The copy of net is unfortunately needed so it does not get overwritten
when mkinitcpio-nfs-utils is updated on the client installation. From
the base mkinitcpio.conf, replace the nfsv3 module with nfsv4, replace
net with net_nfs4, and add /sbin/mount.nfs4 to BINARIES.&lt;/p&gt;
&lt;h4 id=&quot;NBD&quot; name=&quot;NBD&quot;&gt;NBD&lt;/h4&gt;
&lt;p&gt;The mkinitcpio-nbd package needs to be installed on the client.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# pacman --root &quot;$root&quot; --dbpath &quot;$root/var/lib/pacman&quot; -U mkinitcpio-nbd-0.4-1-any.pkg.tar
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You will then need to append nbd to your HOOKS array after net; net
will configure your networking for you, but not attempt a NFS mount if
nfsroot is not specified in the kernel line.&lt;/p&gt;
&lt;h3 id=&quot;Client+configuration&quot; name=&quot;Client+configuration&quot;&gt;Client configuration&lt;/h3&gt;
&lt;p&gt;In addition to the setup mentioned here, you should also set up your
hostname, timezone, locale, and keymap , and follow any other relevant
parts of the Installation Guide .&lt;/p&gt;
&lt;h2 id=&quot;Bootloader&quot; name=&quot;Bootloader&quot;&gt;Bootloader&lt;/h2&gt;
&lt;h3 id=&quot;Pxelinux&quot; name=&quot;Pxelinux&quot;&gt;Pxelinux&lt;/h3&gt;
&lt;p&gt;Install syslinux .&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# pacman -Syu syslinux
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Copy the pxelinux bootloader (provided by the syslinux package) to the
boot directory of the client.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# cp /usr/lib/syslinux/pxelinux.0 &quot;$root/boot&quot;
# mkdir &quot;$root/boot/pxelinux.cfg&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We also created the pxelinux.cfg directory, which is where pxelinux
searches for configuration files by default. Because we don&#039;t want to
discriminate between different host MACs, we then create the default
configuration.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# vim &quot;$root/boot/pxelinux.cfg/default&quot;

default linux

label linux
kernel vmlinuz-linux
append initrd=initramfs-linux.img ip=:::::eth0:dhcp nfsroot=10.0.0.1:/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;NFSv3 mountpoints are relative to the root of the server, not fsid=0.
If you&#039;re using NFSv3, you&#039;ll need to pass 10.0.0.1:/srv/arch to
nfsroot. Or if you are using NBD, use the following append line:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;append ro initrd=initramfs-linux.img ip=:::::eth0:dhcp nbd_host=10.0.0.1 nbd_name=arch root=/dev/nbd0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note: You will need to change nbd_host and/or nfsroot, respectively,
to match your network configuration (the address of the NFS/NBD server)
The pxelinux configuration syntax identical to syslinux; refer to the
upstream documentation for more information. The kernel and initramfs
will be transferred via TFTP, so the paths to those are going to be
relative to the TFTP root. Otherwise, the root filesystem is going to
be the NFS mount itself, so those are relative to the root of the NFS server.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# vim &quot;$root/etc/fstab&quot;

/dev/nbd0  /  btrfs  rw,noatime,discard,compress=lzo  0 0
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Program+state+directories&quot; name=&quot;Program+state+directories&quot;&gt;Program state directories&lt;/h3&gt;
&lt;p&gt;You could mount /var/log, for example, as tmpfs so that logs from
multiple hosts don&#039;t mix unpredictably, and do the same with
/var/spool/cups, so the 20 instances of cups using the same spool
don&#039;t fight with each other and make 1,498 print jobs and eat an entire
ream of paper (or worse: toner cartridge) overnight.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# vim &quot;$root/etc/fstab&quot;
tmpfs   /var/log        tmpfs     nodev,nosuid    0 0
tmpfs   /var/spool/cups tmpfs     nodev,nosuid    0 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It would be best to configure software that has some sort of
state/database to use unique state/database storage directories for
each host. If you wanted to run puppet , for example, you could simply
use the %H specifier in the puppet unit file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# vim &quot;$root/etc/systemd/system/puppetagent.service&quot;
[Unit]
Description=Puppet agent
Wants=basic.target
After=basic.target network.target

[Service]
Type=forking
PIDFile=/run/puppet/agent.pid
ExecStartPre=/usr/bin/install -d -o puppet -m 755 /run/puppet
ExecStart=/usr/bin/puppet agent --vardir=/var/lib/puppet-%H --ssldir=/etc/puppet/ssl-%H

[Install]
WantedBy=multi-user.target
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Puppet-agent creates vardir and ssldir if they do not exist. If neither
of these approaches are appropriate, the last sane option would be to
create a systemd generator that creates a mount unit specific to the
current host (specifiers are not allowed in mount units, unfortunately).&lt;/p&gt;</content>
</entry>
<entry>
<title>OpenWRT web</title>
<link href="https://www.0ink.net/posts/2013/2013-05-30-openwrt-tips.html"></link>
<id>urn:uuid:dfbecfe9-a885-2792-79a7-986e92927c6a</id>
<updated>2022-01-10T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Some useful tidbits to use when using the OpenWRT embedded web
server (uHTTPD).
Embedded Lua
uHTTPd supports running Lua in-process, which can speed up Lua CGI
scripts. It is unclear whether LuCI supports running in this embedded
interpreter. LuCI seems to work fine (if not better) with the embedded
...]]></summary>
<content type="html">&lt;p&gt;Some useful tidbits to use when using the OpenWRT embedded web
server (uHTTPD).&lt;/p&gt;
&lt;h2&gt;Embedded Lua&lt;/h2&gt;
&lt;p&gt;uHTTPd supports running Lua in-process, which can speed up Lua CGI
scripts. It is unclear whether LuCI supports running in this embedded
interpreter. LuCI seems to work fine (if not better) with the embedded
Lua interpreter.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;root@OpenWrt:~# opkg install uhttpd-mod-lua
Installing uhttpd-mod-lua (18) to root...
Downloading http://downloads.openwrt.org/snapshots/trunk/ar71xx/packages/uhttpd-mod-lua_18_ar71xx.ipk.
Configuring uhttpd-mod-lua.
root@OpenWrt:~# uci set uhttpd.main.lua_prefix=/lua
root@OpenWrt:~# uci set uhttpd.main.lua_handler=/root/test.lua
root@OpenWrt:~# cat /root/test.lua
function handle_request(env)
        uhttpd.send(&quot;HTTP/1.0 200 OKrn&quot;)
        uhttpd.send(&quot;Content-Type: text/plainrnrn&quot;)
        uhttpd.send(&quot;Hello world.n&quot;)
end
root@OpenWrt:~# /etc/init.d/uhttpd restart
root@OpenWrt:~# wget -qO- http://127.0.0.1/lua/
Hello world.
root@OpenWrt:~#&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tested on Backfire 10.03.1 with uHTTPd 28.&lt;/p&gt;
&lt;h2&gt;HTTPS Enable and Certificate Settings and Creation&lt;/h2&gt;
&lt;p&gt;First of all, you need to install the &lt;code&gt;uhttpd-mod-tls&lt;/code&gt; package in order to pull into the system the &#039;TLS plugin which adds HTTPS support to uHTTPd&#039;. Then if listen_https is defined in the server configuration, the certificate and private key is missing. In this case (as of 10.03.1) you&#039;ll need to install the &lt;code&gt;luci-ssl&lt;/code&gt; meta-package which in turn will pull also the &lt;code&gt;px5g&lt;/code&gt; script. With this utility the init script will generate the appropriate certifcate and key files when the server is started for the first time, either by reboot or by manual restart. The &lt;code&gt;/etc/config/uhttpd&lt;/code&gt; file contains in the end a section detailing the certificate and key files creation parameters:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Required&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;days&lt;/td&gt;
&lt;td&gt;integer&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;730&lt;/td&gt;
&lt;td&gt;Validity time of the generated certificates in days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bits&lt;/td&gt;
&lt;td&gt;integer&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;1024&lt;/td&gt;
&lt;td&gt;Size of the generated RSA key in bits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;country&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;DE&lt;/td&gt;
&lt;td&gt;ISO country code of the certificate issuer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;state&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;Berlin&lt;/td&gt;
&lt;td&gt;State of the certificate issuer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;location&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;Berlin&lt;/td&gt;
&lt;td&gt;Location/city of the certificate issuer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;commonname&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;OpenWrt&lt;/td&gt;
&lt;td&gt;Common name covered by the certificate&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Those will be needed only once, at the next restart.&lt;/p&gt;
&lt;h2&gt;Basic Authentication (httpd.conf)&lt;/h2&gt;
&lt;p&gt;For backward compatibility reasons, uhttpd uses the old Busybox httpd
config file /&lt;code&gt;etc/httpd.conf&lt;/code&gt; to define authentication areas and the
associated usernames and passwords. This configuration file is not in
UCI format and usually shipped or generated by external packages like
webif (X-Wrt). Authentication realms are defined in the format
&lt;code&gt;prefix:username:password&lt;/code&gt; with one entry per line followed by a
newline.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;prefix is the URL part covered by the realm, e.g. /cgi-bin to request basic auth for any CGI program&lt;/li&gt;
&lt;li&gt;username specifies the username a client has to login with&lt;/li&gt;
&lt;li&gt;password defines the secret password required to authenticate&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The password can be either in plain text format, MD5 encoded or in the
form $p$user where user refers to an account in /etc/shadow or
/etc/passwd. A plain text password can be converted to MD5 encoding by
using the -m switch of the uhttpd executable:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;root@OpenWrt:~# uhttpd -m secret
$1$$ysVNzQc4CTMkp5daOdZ.3/&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the $p$- format is used, uhttpd will compare the client provided
password against the one stored in the shadow or passwd database.&lt;br /&gt;
URL decoding  &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note that this creates a empty salt!&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;URL decoding&lt;/h2&gt;
&lt;p&gt;Like Busybox HTTPd, the URL decoding of strings on the command line is supported through the -d switch:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;root@OpenWrt:/# uhttpd -d &quot;An%20URL%20encoded%20String%21%0a&quot;
An URL encoded String!&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>Program Documentation</title>
<link href="https://www.0ink.net/posts/2013/2013-05-29-program-documentation.html"></link>
<id>urn:uuid:711cca73-34c6-0b20-a7bf-2990a7fef1dd</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[So these are my ideas on how to document projects. There are three types of documentation types:

User guides
Targetted and end-users of the software and people who want a brief overview.
Man pages
Again targetted at end-users but also sysadmins. Usually to address a specific feature.
...]]></summary>
<content type="html">&lt;p&gt;So these are my ideas on how to document projects. There are three types of documentation types:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;User guides&lt;br /&gt;
Targetted and end-users of the software and people who want a brief overview.&lt;/li&gt;
&lt;li&gt;Man pages&lt;br /&gt;
Again targetted at end-users but also sysadmins. Usually to address a specific feature.&lt;/li&gt;
&lt;li&gt;API level documentation/reference guide.&lt;br /&gt;
Targetted at programmers enhancing, maintaining the software.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To generate I would use different tools. In general we would like to embed with source code.&lt;/p&gt;
&lt;h2 id=&quot;User+Guides&quot; name=&quot;User+Guides&quot;&gt;User Guides&lt;/h2&gt;
&lt;p&gt;Either as a stand alone document or embedded in the code. My default
tool is to use &lt;a href=&quot;http://en.wikipedia.org/wiki/Markdown&quot;&gt;Markdown&lt;/a&gt; as can
be easily be converted to HTML or PDF as needed.&lt;/p&gt;
&lt;h2 id=&quot;Man+pages&quot; name=&quot;Man+pages&quot;&gt;Man pages&lt;/h2&gt;
&lt;p&gt;Use &lt;code&gt;manify&lt;/code&gt;. Embedded in the source code.&lt;/p&gt;
&lt;h2 id=&quot;API+reference+documentation&quot; name=&quot;API+reference+documentation&quot;&gt;API reference documentation&lt;/h2&gt;
&lt;p&gt;We need generation tools. So the candidates are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;C :
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.khm.de/~rudi/ZehDok/&quot;&gt;zehdok&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/angelortega/mp_doccer&quot;&gt;mp_doccer&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;php : Multiples
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.phpdoc.org/&quot;&gt;phpdoc&lt;/a&gt;: The main one&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/peej/phpdoctor&quot;&gt;peej&#039;s phpdoctor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.apigen.org/&quot;&gt;ApiGen&lt;/a&gt; : more modern alternative.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/victorjonsson/PHP-Markdown-Documentation-Generator&quot;&gt;PHP Markdown doc generator&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;perl : &lt;a href=&quot;http://juerd.nl/site.plp/perlpodtut&quot;&gt;pod&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;python : &lt;a href=&quot;http://docs.python.org/2/library/pydoc.html&quot;&gt;pydoc&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;tcl : &lt;a href=&quot;http://tcl.jtang.org/tcldoc/tcldoc/&quot;&gt;tcldoc&lt;/a&gt; or &lt;a href=&quot;http://www.doxygen.org&quot;&gt;doctools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;java : &lt;a href=&quot;http://www.oracle.com/technetwork/java/javase/documentation/index-137868.html&quot;&gt;javadoc&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;javascript : &lt;a href=&quot;http://code.google.com/p/jsdoc-toolkit/&quot;&gt;jsdoc-toolkit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Shell script: &lt;a href=&quot;https://github.com/alejandroliu/ashlib/blob/master/shdoc&quot;&gt;shdoc&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Multi languages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.doxygen.org&quot;&gt;doxygen&lt;/a&gt;: C, Objective-C, C#, PHP, Java, Python, IDL (Corba, Microsoft, and UNO/OpenOffice flavors), Fortran, VHDL, Tcl, and to some extent D.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://rfsber.home.xs4all.nl/Robo/?&quot;&gt;ROBODoc&lt;/a&gt;: Virtually anything.&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Git Tutorials</title>
<link href="https://www.0ink.net/posts/2013/2013-05-28-git-tutorials.html"></link>
<id>urn:uuid:7f3a0414-3f64-6459-f95f-31938609b9d9</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Reference for Git tutorials

http://linux.yyz.us/git-howto.html
http://git.or.cz/course/svn.html
http://spheredev.org/wiki/Git_for_the_lazy
http://www.kernel.org/pub/software/scm/git/docs/gittutorial.html
...]]></summary>
<content type="html">&lt;p&gt;Reference for Git tutorials&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://linux.yyz.us/git-howto.html&quot;&gt;http://linux.yyz.us/git-howto.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://git.or.cz/course/svn.html&quot;&gt;http://git.or.cz/course/svn.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://spheredev.org/wiki/Git_for_the_lazy&quot;&gt;http://spheredev.org/wiki/Git_for_the_lazy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.kernel.org/pub/software/scm/git/docs/gittutorial.html&quot;&gt;http://www.kernel.org/pub/software/scm/git/docs/gittutorial.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.kernel.org/pub/software/scm/git/docs/everyday.html&quot;&gt;http://www.kernel.org/pub/software/scm/git/docs/everyday.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://git.wiki.kernel.org/index.php/GitSubmoduleTutorial&quot;&gt;https://git.wiki.kernel.org/index.php/GitSubmoduleTutorial&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Remote Bridging</title>
<link href="https://www.0ink.net/posts/2013/2013-05-27-remote-bridging.html"></link>
<id>urn:uuid:054c22b3-b442-9648-b876-c96dddab08c4</id>
<updated>2024-10-23T00:00:00+02:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[Sometimes we need to connect two or more geographically distributed ethernet networks to one broadcast domain. There can be two different office networks of some company which uses smb protocol partially based on broadcast network messages. Another example of such situation is computer cafes: a couple of computer cafes can provide to users more convinient environment forr playing multiplayer computer games without dedicated servers. Both sample networks in this article need to have one *nix server for bridging. Our networks can be connected by any possible hardware that provides IP connection between them.
Connecting Two Remote Local Networks With Transparent Bridging Technique
Short description
In described configuration we are connecting two remote LANs to make them appearing as one network with 192.168.1.0/24 address space (however physically, presense of bridges in network configuration is not affecting IP protocol and is fully transparent for it, so you can freely select any address space). Both of the bridging servers has two network interfaces: one (as eth0 in our example) connested to the LAN, and second (eth1) is being used as transport to connect networks. When ethernet tunnel between gateways in both networks will be bringed up we will connect tunnel interfaces with appropriate LAN interfaces with bridge interfaces. Schematically this configuration can be following:


...]]></summary>
<content type="html">&lt;p&gt;Sometimes we need to connect two or more geographically distributed ethernet networks to one broadcast domain. There can be two different office networks of some company which uses smb protocol partially based on broadcast network messages. Another example of such situation is computer cafes: a couple of computer cafes can provide to users more convinient environment forr playing multiplayer computer games without dedicated servers. Both sample networks in this article need to have one *nix server for bridging. Our networks can be connected by any possible hardware that provides IP connection between them.&lt;/p&gt;
&lt;h1&gt;Connecting Two Remote Local Networks With Transparent Bridging Technique&lt;/h1&gt;
&lt;h2&gt;Short description&lt;/h2&gt;
&lt;p&gt;In described configuration we are connecting two remote LANs to make them appearing as one network with 192.168.1.0/24 address space (however physically, presense of bridges in network configuration is not affecting IP protocol and is fully transparent for it, so you can freely select any address space). Both of the bridging servers has two network interfaces: one (as eth0 in our example) connested to the LAN, and second (eth1) is being used as transport to connect networks. When ethernet tunnel between gateways in both networks will be bringed up we will connect tunnel interfaces with appropriate LAN interfaces with bridge interfaces. Schematically this configuration can be following:&lt;/p&gt;
&lt;div&gt;&lt;svg class=&quot;bob&quot; font-family=&quot;arial&quot; font-size=&quot;14&quot; height=&quot;96&quot; width=&quot;568&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;defs&gt;
&lt;marker id=&quot;triangle&quot; markerHeight=&quot;10&quot; markerUnits=&quot;strokeWidth&quot; markerWidth=&quot;10&quot; orient=&quot;auto&quot; refX=&quot;15&quot; refY=&quot;10&quot; viewBox=&quot;0 0 50 20&quot;&gt;
&lt;path d=&quot;M 0 0 L 30 10 L 0 20 z&quot;/&gt;
&lt;/marker&gt;
&lt;/defs&gt;
&lt;style&gt;

    line, path {
      stroke: black;
      stroke-width: 2;
      stroke-opacity: 1;
      fill-opacity: 1;
      stroke-linecap: round;
      stroke-linejoin: miter;
    }
    circle {
      stroke: black;
      stroke-width: 2;
      stroke-opacity: 1;
      fill-opacity: 1;
      stroke-linecap: round;
      stroke-linejoin: miter;
    }
    circle.solid {
      fill:black;
    }
    circle.open {
      fill:transparent;
    }
    tspan.head{
        fill: none;
        stroke: none;
    }
    
&lt;/style&gt;
&lt;path d=&quot; M 116 8 L 120 8 M 116 8 L 116 16 M 120 8 L 128 8 M 120 8 L 128 8 L 136 8 M 128 8 L 136 8 L 144 8 M 136 8 L 144 8 L 152 8 M 144 8 L 152 8 L 160 8 M 152 8 L 160 8 L 168 8 M 160 8 L 168 8 L 176 8 M 168 8 L 176 8 M 180 8 L 176 8 M 180 8 L 180 16 M 372 8 L 376 8 M 372 8 L 372 16 M 376 8 L 384 8 M 376 8 L 384 8 L 392 8 M 384 8 L 392 8 L 400 8 M 392 8 L 400 8 L 408 8 M 400 8 L 408 8 L 416 8 M 408 8 L 416 8 L 424 8 M 416 8 L 424 8 L 432 8 M 424 8 L 432 8 M 436 8 L 432 8 M 436 8 L 436 16 M 116 16 L 116 32 M 116 16 L 116 32 M 180 16 L 180 32 M 180 16 L 180 32 M 372 16 L 372 32 M 372 16 L 372 32 M 436 16 L 436 32 M 436 16 L 436 32 M 116 40 L 116 32 M 116 40 L 120 40 L 128 40 M 120 40 L 128 40 L 136 40 M 128 40 L 136 40 L 144 40 M 136 40 L 144 40 L 152 40 M 144 40 L 152 40 L 160 40 M 152 40 L 160 40 L 168 40 M 160 40 L 168 40 L 176 40 M 168 40 L 176 40 M 180 40 L 180 32 M 180 40 L 176 40 M 372 40 L 372 32 M 372 40 L 376 40 L 384 40 M 376 40 L 384 40 L 392 40 M 384 40 L 392 40 L 400 40 M 392 40 L 400 40 L 408 40 M 400 40 L 408 40 L 416 40 M 408 40 L 416 40 L 424 40 M 416 40 L 424 40 L 432 40 M 424 40 L 432 40 M 436 40 L 436 32 M 436 40 L 432 40 M 124 48 L 124 64 M 124 48 L 124 64 M 172 48 L 172 64 M 172 48 L 172 64 M 380 48 L 380 64 M 380 48 L 380 64 M 428 48 L 428 64 M 428 48 L 428 64 M 124 64 L 124 80 M 124 64 L 124 80 M 172 64 L 172 80 M 172 64 L 172 80 M 380 64 L 380 80 M 380 64 L 380 80 M 428 64 L 428 80 M 428 64 L 428 80 M 32 88 L 40 88 M 32 88 L 40 88 L 48 88 M 40 88 L 48 88 L 56 88 M 48 88 L 56 88 L 64 88 M 56 88 L 64 88 L 72 88 M 64 88 L 72 88 L 80 88 M 72 88 L 80 88 L 88 88 M 80 88 L 88 88 L 96 88 M 88 88 L 96 88 L 104 88 M 96 88 L 104 88 L 112 88 M 104 88 L 112 88 M 192 88 L 200 88 M 192 88 L 200 88 L 208 88 M 200 88 L 208 88 L 216 88 M 208 88 L 216 88 M 344 88 L 352 88 M 344 88 L 352 88 L 360 88 M 352 88 L 360 88 L 368 88 M 360 88 L 368 88 M 448 88 L 456 88 M 448 88 L 456 88 L 464 88 M 456 88 L 464 88 L 472 88 M 464 88 L 472 88 L 480 88 M 472 88 L 480 88 L 488 88 M 480 88 L 488 88 L 496 88 M 488 88 L 496 88 L 504 88 M 496 88 L 504 88 L 512 88 M 504 88 L 512 88 L 520 88 M 512 88 L 520 88 L 528 88 M 520 88 L 528 88 L 536 88 M 528 88 L 536 88 L 544 88 M 536 88 L 544 88 L 552 88 M 544 88 L 552 88 L 560 88 M 552 88 L 560 88 L 568 88 M 560 88 L 568 88&quot; fill=&quot;none&quot;/&gt;
&lt;path d=&quot;&quot; fill=&quot;none&quot; stroke-dasharray=&quot;3 3&quot;/&gt;
&lt;text x=&quot;137&quot; y=&quot;28&quot;&gt;
br0
&lt;/text&gt;
&lt;text x=&quot;393&quot; y=&quot;28&quot;&gt;
br0
&lt;/text&gt;
&lt;text x=&quot;33&quot; y=&quot;76&quot;&gt;
Network
&lt;/text&gt;
&lt;text x=&quot;97&quot; y=&quot;76&quot;&gt;
1
&lt;/text&gt;
&lt;text x=&quot;457&quot; y=&quot;76&quot;&gt;
Network
&lt;/text&gt;
&lt;text x=&quot;521&quot; y=&quot;76&quot;&gt;
2
&lt;/text&gt;
&lt;text x=&quot;113&quot; y=&quot;92&quot;&gt;
eth0
&lt;/text&gt;
&lt;text x=&quot;161&quot; y=&quot;92&quot;&gt;
tap0
&lt;/text&gt;
&lt;text x=&quot;217&quot; y=&quot;92&quot;&gt;
eth1........eth1
&lt;/text&gt;
&lt;text x=&quot;369&quot; y=&quot;92&quot;&gt;
tap0
&lt;/text&gt;
&lt;text x=&quot;417&quot; y=&quot;92&quot;&gt;
eth0
&lt;/text&gt;
&lt;/svg&gt;
&lt;/div&gt;
&lt;h1&gt;Setting Up Bridging Servers&lt;/h1&gt;
&lt;p&gt;&lt;em&gt;Notice: This article describes Debian GNU/Linux servers setup. If you are using another distribution, there can be some differences in network configuration and package management, but the main idea of described actions will be the same.&lt;/em&gt; First of all, we need to check if tun and bridge modules is not included in current kernel. If they are not includen, we need to rebuild kernel with CONFIG_TUN and CONFIG_BRIDGE options. Next, we need to create tunnel device file for our tunnel:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# cd /dev
# ./MAKEDEV tun
# mkdir misc
# ln -s /dev/net /dev/misc/net&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Notice: Last command is needed to make vtun work, because authors build for debian is looking for tunnel device driver at /dev/misc/net/tun.&lt;/em&gt; To create ethernet tunnel between bridging servers we will use &lt;strong&gt;vtun&lt;/strong&gt; software. When &lt;code&gt;vtun&lt;/code&gt; will be installed, we will need to select one of the bridging servers as master and second server will be slave and appropriately change vtund-start.conf and vtund.conf file in /etc/ on buth servers. Complete config files for master is following.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/etc/vtund-start.conf
----cut-here------------------------------------
--server-- 5000
----cut-here------------------------------------

/etc/vtund.conf
----cut-here------------------------------------
options {
    port 5000;            # Listen on this port.

    # Syslog facility
    syslog        daemon;

    # Path to various programs
    ifconfig      /sbin/ifconfig;
    route         /sbin/route;
    firewall      /sbin/iptables;
    ip            /sbin/ip;
}

default {
    compress no;
    encrypt no;
    speed 0;
}

rembridge {
    passwd Pa$$Wd;
    type ether;
    proto udp;
    keepalive yes;
    compress no;
    encrypt yes;

    up {
        # Connection is Up
        ifconfig &quot;%% up&quot;;
        program &quot;brctl addif br0 %%&quot;;
    };

    down {
        # Connection is Down
        ifconfig &quot;%% down&quot;;
    };
}
----cut-here------------------------------------&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Slave server config files is following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/etc/vtund-start.conf
----cut-here------------------------------------
rembridge 10.1.1.1 -p
----cut-here------------------------------------&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Notice: In this example 10.1.1.1 is transport address of master server.&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/etc/vtund.conf
----cut-here------------------------------------
options {
  # Path to various programs
  ifconfig      /sbin/ifconfig;
  route         /sbin/route;
  firewall      /sbin/iptables;
}

korsar {
  pass  Pa$$Wd;         # Password
  type  ether;          # Ethernet tunnel
  up {
        # Connection is Up
        ifconfig &quot;%% up&quot;;
        program &quot;brctl addif br0 %%&quot;
  };
  down {
        # Connection is Down
        ifconfig &quot;%% down&quot;;
  };
}
----cut-here------------------------------------&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To bring up bridge between LAN ethernet interface and our newly created tunnel interface we need to create bridge interface. To complete this task we will add br0 interface description to /etc/network/interfaces file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;auto br0
iface br0 inet static
    address 192.168.1.199
    netmask 255.255.255.0
    bridge_ports eth0&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Notice: IP-addresses on both sides of our bridge must be unique in both networks. eth0 is LAN interface.&lt;/em&gt; Now, we need to bring this interface up:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# ifup br0&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When br0 interface will be created, we will be able to start vtun. # /etc/init.d/vtund restart If everything was done correctly, we will see following results on both sersers (br0 and tap0 interfaces):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# ifconfig tap0
tap0  Link encap:Ethernet  HWaddr 00:FF:B2:91:CA:DE
      UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
      RX packets:701818 errors:0 dropped:0 overruns:0 frame:0
      TX packets:405939 errors:0 dropped:0 overruns:0 carrier:0
      collisions:0 txqueuelen:1000
      RX bytes:975889241 (930.6 MiB)  TX bytes:44704104 (42.6 MiB)

# ifconfig br0
br0   Link encap:Ethernet  HWaddr 00:02:44:2A:03:30
      inet addr:192.168.1.199  Bcast:192.168.1.255  Mask:255.255.255.0
      UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
      RX packets:2660 errors:0 dropped:0 overruns:0 frame:0
      TX packets:42 errors:0 dropped:0 overruns:0 carrier:0
      collisions:0 txqueuelen:0
      RX bytes:239368 (233.7 KiB)  TX bytes:2338 (2.2 KiB)

#&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we need to see current state of bridge interface, we can use brctl tool:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# brctl show br0
bridge name     bridge id               STP enabled     interfaces
br0             8000.0002442a0330       no              eth0
                                                        tap0
#&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When all of described steps will be completed, our computers in both networks will be able to communicate with each other. IP addresses on bridge interfaces can be used for troubleshooting network connection. And last, if you need, you can turn on compression or enrtyption of data within created tunnel.&lt;/p&gt;</content>
</entry>
<entry>
<title>Network wiring notes - 8P8C / RJ45</title>
<link href="https://www.0ink.net/posts/2013/2013-05-27-network-wiring-notes-8p8c-rj45.html"></link>
<id>urn:uuid:98e90f61-1c12-cb31-fb03-600754885757</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[What you were probably looking forT568A/B (10-BASE-T and 100-BASE-TX):
With pin positions are counted from left to right with the contacts facing you (clip on the back) and pointing up (cable coming out the bottom):



Color (568B)
...]]></summary>
<content type="html">&lt;p&gt;What you were probably looking forT568A/B (10-BASE-T and 100-BASE-TX):&lt;/p&gt;
&lt;p&gt;With pin positions are counted from &lt;em&gt;left to right&lt;/em&gt; with the &lt;em&gt;contacts facing&lt;/em&gt; you (clip on the back) and pointing up &lt;em&gt;(cable coming out the bottom):&lt;/em&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Color (568B)&lt;/th&gt;
&lt;th&gt;Pin&lt;/th&gt;
&lt;th&gt;Color(568A)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Orange-white&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Green-white&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Orange&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Green&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Green-white&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Orange-white&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blue&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Blue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blue-white&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Blue-white&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Green&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Orange&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Brown-white&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Brown-white&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Brown&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Brown&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;img src=&quot;/images/2013/wiring1.jpg&quot; alt=&quot;wiring1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Cut the outer insulation and order the wires (right), cut for equal length (not shown), and insert into plug (left)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/2013/wiring2.jpg&quot; alt=&quot;wiring1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Check that the wires go to the end of the plug by seeing if you can see each wire at the end, preferably see its copper reflecting (left).&lt;/p&gt;
&lt;p&gt;Then use the crimping tool (shoves the pins into the wires, and fixes the wire in the plug.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;100Mbit ethernet&lt;/em&gt; (Fast Ethernet, FE) uses only two pairs - pins 1, 2, 3, and 6, which in the standard wiring are the orange and green wire pairs.&lt;/p&gt;
&lt;p&gt;This means you could use the other wires for different things - e.g. one link and a phone (non-conflicting if you use the stand pins for each), or some more custom combination such as two links though one wire.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Gigabit ethernet&lt;/em&gt; uses all four pairs, so has no creative options.&lt;/p&gt;
&lt;h1&gt;On crossover cables&lt;/h1&gt;
&lt;p&gt;Given the two plug colorings in 568-type wiring, cables wired with these can be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;em&gt;straight cable&lt;/em&gt;, 568B-568B (or the functionally equivalent 568A-568A, but in terms of colors the B-B variant seems to be used everywhere, probably to avoid confusion)&lt;/li&gt;
&lt;li&gt;A &lt;em&gt;crossover cable&lt;/em&gt; (also &lt;em&gt;patch cable&lt;/em&gt;) has 568A on one end and 568B on the other. (crossing is also effectively done by a switch or hub, so you can use straight cables except in cases where you don&#039;t use switches. Crossovers can be useful for direct computer-computer connections).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Gigabit ethernet doesn&#039;t need crossovers -- it decided to handle that case inside the NIC and switches rather than have you do it the cable. You use straight cables everywhere (NIC-switch-NIC and NIC-NIC).&lt;/p&gt;
&lt;p&gt;Gigabit crossovers are rumored to exist (crossing blue and brown in addition to orange and green), but they are unnecessary.&lt;/p&gt;
&lt;h1&gt;On Loopbacks&lt;/h1&gt;
&lt;p&gt;Loopbacks connect a port to itself. This can be used to test whether a long cable and/or its wallplug is broken, and whether a switch/router port is broken (or perhaps dirty or corroded), both just by seeing whether the link light comes on.&lt;/p&gt;
&lt;p&gt;Connect:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pin 1 to 3&lt;/li&gt;
&lt;li&gt;Pin 2 to 6&lt;/li&gt;
&lt;li&gt;Pin 4 to 7 (for a gBit loopback)&lt;/li&gt;
&lt;li&gt;Pin 5 to 8 (for a gBit loopback)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(If you&#039;re wiring a plug as a loopback, make sure you&#039;re not confused about which end is pin is pin 1). To create a loopback from a plug-with-cable you cut (that was wired according to 568A or 568B), this means:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Orange-white  to  Green-white
Orange        to  Green
Blue          to  Brown-white  (for a gBit loopback)
Brown         to  Blue-white   (for a gBit loopback)&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;gBit loopback is a limited concept:&lt;/h1&gt;
&lt;p&gt;Gigabit NICs have crosstalk detection (detects how much signal interferes onto other wires), and will likely decide that the loopback is an extreme amount of crosstalk - any may not show link. Meaning it&#039;s often only useful on NICs which let you disable crosstalk detection. Gigabit switches may behave differently (but I&#039;m not sure what the spec says or the real-world variation is)&lt;/p&gt;
&lt;p&gt;More notes on ethernet wiring&lt;/p&gt;
&lt;p&gt;The wiring used on 10Mbit, 100Mbit (specifically 10-BASE-T and 100-BASE-TX) ethernet over 8P8C (informally RJ45) plugs is defined by &lt;em&gt;TIA/EIA-568-B&lt;/em&gt;, which define two plug wiring alternatives, 568A and 568B. Notice the lack of dashes; 568-B is the standard they are part of, 568-A a completely different standard (yes, that naming is stupidly confusing).&lt;/p&gt;
&lt;p&gt;Note that both 10Mbit and 100Mbit networking use only pair 2 and 3 (orange and green) in the standard (The blue pair is pair 1, orange is pair 2, green is pair 3, and brown is pair 4.)&lt;/p&gt;
&lt;p&gt;This means that Americans or anyone else using 4P/6P-style phone connectors can use fully wired cables (most are fully wired - relatively few (cheaper) cables are only two-pair for only Ethernet) to wire their house/company and have the same sockets be usable to plug in phones, a computer (or both with a trivial splitter). Various companies can use use this to make their wiring simpler.&lt;/p&gt;
&lt;h1&gt;Gigabit ethernet&lt;/h1&gt;
&lt;p&gt;GBit ethernet can use cables wired 568-style, preferably rated Cat5e, or better.&lt;/p&gt;
&lt;p&gt;Specifically, you want straight wiring and four-pair cable. Most older cables are, so can be used at gBit speeds, as 1000-BASE-T uses all four pairs instead of just two.&lt;/p&gt;
&lt;p&gt;(If you press your own plugs, it is suggested that you keep the untwisted length as small as possible, to minimize near-end crosstalk)&lt;/p&gt;
&lt;p&gt;Some implications:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;you can&#039;t do the phone/networking split mentioned above&lt;/li&gt;
&lt;li&gt;on-the-cheap two-pair cables will work, but only because the NICs fall back to 100mbit&lt;/li&gt;
&lt;li&gt;you can mix 10/100/1000 in your network, by replacing switches (handy for partial/gradual upgrades), without having to wire about the cabling.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;...as long as the cable is rated Cat5e (or better)&lt;/p&gt;
&lt;h1&gt;On cable standards (Cat5, etc.)&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Cat5
&lt;ul&gt;
&lt;li&gt;rarely seen - it has fallen out of favour and is barely sold&lt;/li&gt;
&lt;li&gt;(...but your company may still be wired with it)&lt;/li&gt;
&lt;li&gt;regularly and informally refers to Cat5a&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Cat5a
&lt;ul&gt;
&lt;li&gt;Currently still quite common&lt;/li&gt;
&lt;li&gt;100mBit, 1gBit at &amp;lt;100m&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Cat6
&lt;ul&gt;
&lt;li&gt;100mBit, 1gBit at &amp;lt;100m&lt;/li&gt;
&lt;li&gt;&amp;lt;55m for 10gBit (less if many are bundled)&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Cat6a
&lt;ul&gt;
&lt;li&gt;10GBit at &amp;lt;100m&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Cat7 - stricter about crosstalk (pairs individually insulated)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Any cabling that is not shielded will crosstalk, meaning permissible distances are lower when many are bundled (relevant to company wiring), or be likely to get a lot of outside interference.&lt;/p&gt;
&lt;p&gt;The Shiny Special Expensive cables sold in the sort of computer shops that only sell things that come in plastic boxes are generally not necessary, particularly not on the few-meter cables for your home LAN.&lt;/p&gt;
&lt;p&gt;For example, Cat6 was made for 10gBit, most single computers don&#039;t have a source of data to actually use that speed, and even if they had, it&#039;s hard to get a 10gBit switch.&lt;/p&gt;
&lt;p&gt;Companies may want Cat6 (or perhaps Cat6a) for future compatibility, to be able to use gBit now and assuming that nothing replaces copper before the next update.&lt;/p&gt;
&lt;p&gt;Cat7 can go beyond 10gBit at short-ish distances, but chances are you won&#039;t be needing that any time soon. Only data centers might care.&lt;/p&gt;
&lt;h1&gt;Naming pendantics and telephony&lt;/h1&gt;
&lt;p&gt;When we say RJ45, we often mean something like &amp;quot;Ethernet wiring on an 8P8C plug.&amp;quot;&lt;/p&gt;
&lt;p&gt;RJ is the group of plugs that can be described by their positions and connectors, such as 8P8C in Ethernet, while RJ45 actually refers to a specific telephone wiring on the 8P-style plug (probably the most common one among several), while 8P8C refers to that plug itself and no specific wiring. Regardless, most people call the plug RJ45, regardless of wiring.&lt;/p&gt;
&lt;p&gt;Plugs may have fewer actually present conductors than they have positions, so 8P2C, 8P4C, 8P6C, 8P8C, 6P2C, 6P4C, 6P6C, 4P2C, 4P4C all exist.&lt;/p&gt;
&lt;p&gt;When there are less connectors than positions, they are in the middle positions; RJ-style wiring is from the middle out.&lt;/p&gt;
&lt;p&gt;For most of us, the is interesting only in that you can plug a phone with 6P plugs into a 8P (ethernet-plus-phone) socket and have the phone work - the clip aligns the plug in the middle.&lt;/p&gt;
&lt;p&gt;If more more than the middle two wires are used in telephone wiring, they carry either power, or a second (RJ14) or even third (RJ25) telephone line on the same wire, but consumers rarely see this type of phone wiring)}}&lt;/p&gt;
&lt;p&gt;There are exceptions to the &#039;always start in the middle&#039;, but they tend to be intentionally working around RJ-style wiring.&lt;/p&gt;
&lt;p&gt;The most common use of 6P outside of the US is probably phone wiring according to RJ11, which often use just a single pair in the middle. In the US, 8P connectors with the RJ45 phone wiring is common.&lt;/p&gt;</content>
</entry>
<entry>
<title>Native Kerberos Authentication with SSH</title>
<link href="https://www.0ink.net/posts/2013/2013-05-27-native-kerberos-authentication-with-ssh.html"></link>
<id>urn:uuid:a49b5e99-1930-93e5-99c9-91833d549d70</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This article is about integrating OpenSSH in a kerberos environment.
Allthough OpenSSH can provide passwordless logins (through Public/Private
keys), it is not a true SSO set-up.  This article makes use of
Kerberos TGT service to implement a true SSO configuration for OpenSSH.
Pre-requisites
First off, you'll need to make sure that the OpenSSH server's Kerberos configuration (in /etc/krb5.conf) is correct and works, and that the server's keytab (typically /etc/krb5.keytab) contains an entry for host/fqdn@REALM (case-sensitive). I won't go into details on how this is done again; instead, I'll refer you to any one of the recent Kerberos-related articles (like this one, this one, or even this one). Just be sure that you can issue a kinit -k host/fqdn@REALM and get back a Kerberos ticket without having specify a password. (This tells you that the keytab is working as expected.)
...]]></summary>
<content type="html">&lt;p&gt;This article is about integrating OpenSSH in a kerberos environment.
Allthough OpenSSH can provide passwordless logins (through Public/Private
keys), it is not a true SSO set-up.  This article makes use of
Kerberos TGT service to implement a true SSO configuration for OpenSSH.&lt;/p&gt;
&lt;h2 id=&quot;Pre-requisites&quot; name=&quot;Pre-requisites&quot;&gt;Pre-requisites&lt;/h2&gt;
&lt;p&gt;First off, you&#039;ll need to make sure that the OpenSSH server&#039;s Kerberos configuration (in &lt;code&gt;/etc/krb5.conf&lt;/code&gt;) is correct and works, and that the server&#039;s keytab (typically &lt;code&gt;/etc/krb5.keytab&lt;/code&gt;) contains an entry for &lt;code&gt;host/fqdn@REALM&lt;/code&gt; (case-sensitive). I won&#039;t go into details on how this is done again; instead, I&#039;ll refer you to any one of the recent Kerberos-related articles (like &lt;a href=&quot;http://blog.scottlowe.org/2006/08/08/linux-active-directory-and-windows-server-2003-r2-revisited/&quot;&gt;this one&lt;/a&gt;, &lt;a href=&quot;http://blog.scottlowe.org/2006/08/15/solaris-10-and-active-directory-integration/&quot;&gt;this one&lt;/a&gt;, or &lt;a href=&quot;http://blog.scottlowe.org/2006/08/21/more-on-kerberos-authentication-against-active-directory/&quot;&gt;even this one&lt;/a&gt;). Just be sure that you can issue a &lt;code&gt;kinit -k host/fqdn@REALM&lt;/code&gt; and get back a Kerberos ticket without having specify a password. (This tells you that the keytab is working as expected.)&lt;/p&gt;
&lt;h2 id=&quot;Configuring+the+SSH+Server&quot; name=&quot;Configuring+the+SSH+Server&quot;&gt;Configuring the SSH Server&lt;/h2&gt;
&lt;p&gt;Configure `/etc/ssh/sshd_config with the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; KerberosAuthentication yes
 KerberosTicketCleanup yes
 GSSAPIAuthentication yes
 GSSAPICleanupCredentials yes
 UseDNS yes
 UsePAM no
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If &lt;code&gt;UseDNS&lt;/code&gt; is set to &lt;code&gt;Yes&lt;/code&gt;, the ssh server does a reverse host lookup to find the name of the connecting client. This is necessary when host-based authentication is used or when you want last login information to display host names rather than IP addresses. &lt;em&gt;Note:&lt;/em&gt; Some ssh sessions stall when performing reverse name lookups because the DNS servers are unreachable. If this happens, you can skip the DNS lookups by setting &lt;code&gt;UseDNS&lt;/code&gt; to &lt;code&gt;no&lt;/code&gt;. If &lt;code&gt;UseDNS&lt;/code&gt; is not explicitly set in the &lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt; file, the default value is &lt;code&gt;UseDNS yes&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;Configuring+the+SSH+Client&quot; name=&quot;Configuring+the+SSH+Client&quot;&gt;Configuring the SSH Client&lt;/h2&gt;
&lt;p&gt;Edit &lt;code&gt;/etc/ssh/ssh_config&lt;/code&gt;, and change the file accordingly. For example, we want to enable Kerberos mechanism for all Hosts:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; Host *
      ....
      GSSAPIAuthentication yes
      GSSAPIDelegateCredentials yes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or to enable to specific domains:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Host *.example.com
  GSSAPIAuthentication yes
  GSSAPIDelegateCredentials yes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This limits GSSAPI authentication to only those hosts in the &lt;code&gt;example.com&lt;/code&gt; domain. Modify the domain to be the appropriate domain for your network.&lt;/p&gt;
&lt;h2 id=&quot;Testing+the+Configuration&quot; name=&quot;Testing+the+Configuration&quot;&gt;Testing the Configuration&lt;/h2&gt;
&lt;p&gt;Obtain a valid Kerberos ticket &lt;code&gt;kinit username&lt;/code&gt; from the command line. Once you have a ticket, you should be able to simply &lt;code&gt;ssh fqdn.of.server&lt;/code&gt; and you will get logged in, without getting prompted for a password. If you get prompted for a password, go back and double-check your keytab, your SSH daemon configuration, and the time configuration on your OpenSSH server. Because Kerberos requires time synchronization, differences of greater than 5 minutes will cause the authentication to fail.&lt;/p&gt;</content>
</entry>
<entry>
<title>My new 0ink.net site</title>
<link href="https://www.0ink.net/posts/2013/2013-05-27-my-new-0ink-net-site.html"></link>
<id>urn:uuid:6eb8ae80-18f8-cfaa-507e-1df26e4e30fc</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[So one weekend went by and managed to finish up my 0ink.net web site. So now I have:

wordpress
For main content.
tt-rss
This is my answer to Google's shutdown of the Reader service.
...]]></summary>
<content type="html">&lt;p&gt;So one weekend went by and managed to finish up my &lt;code&gt;0ink.net&lt;/code&gt; web site. So now I have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://wordpress.org/&quot;&gt;wordpress&lt;/a&gt;&lt;br /&gt;
For main content.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://tt-rss.org/&quot;&gt;tt-rss&lt;/a&gt;&lt;br /&gt;
This is my answer to Google&#039;s shutdown of the Reader service.&lt;/li&gt;
&lt;li&gt;Automated backups&lt;br /&gt;
Through my own custom scripts.&lt;/li&gt;
&lt;li&gt;New e-mail server&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.manyfish.co.uk/sitecopy/&quot;&gt;sitecopy&lt;/a&gt;&lt;br /&gt;
To manage the web software updates.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.cloudflare.com/&quot;&gt;CloudFlare&lt;/a&gt;&lt;br /&gt;
Reverse proxy and web accelerator.&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Mirroring a Gitorious repository to GitHub</title>
<link href="https://www.0ink.net/posts/2013/2013-05-27-mirroring-a-gitorious-repository-to-github.html"></link>
<id>urn:uuid:e3c23958-96cc-4ff1-23da-8e34fe3884c0</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[There is nothing special with GitHub and
Gitorious here. This technique would work
exactly the same the other way around or with other servers.
In a nutshell
# Inital setup
git clone --mirror git://gitorious.org/weasyprint/weasyprint.git weasyprint
...]]></summary>
<content type="html">&lt;p&gt;There is nothing special with &lt;a href=&quot;http://github.com/&quot;&gt;GitHub&lt;/a&gt; and
&lt;a href=&quot;http://gitorious.com/&quot;&gt;Gitorious&lt;/a&gt; here. This technique would work
exactly the same the other way around or with other servers.&lt;/p&gt;
&lt;h2 id=&quot;In+a+nutshell&quot; name=&quot;In+a+nutshell&quot;&gt;In a nutshell&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;# Inital setup
git clone --mirror git://gitorious.org/weasyprint/weasyprint.git weasyprint
GIT_DIR=weasyprint git remote add github git@github.com:SimonSapin/WeasyPrint.git

# In cron
cd /path/to/project &amp;amp;&amp;amp; git fetch -q &amp;amp;&amp;amp; git push -q --mirror github
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;How+it+works&quot; name=&quot;How+it+works&quot;&gt;How it works&lt;/h2&gt;
&lt;p&gt;Mirroring with Git is pretty easy: just pull from or push to another
repository. &lt;a href=&quot;http://github.com/&quot;&gt;GitHub&lt;/a&gt; and
&lt;a href=&quot;http://gitorious.com/&quot;&gt;Gitorious&lt;/a&gt; allow you to push to them or pull
from them, but you can not make them push to somewhere else. You need
something in the middle. Digging a bit in the man pages tells you that
the magic option is &lt;code&gt;--mirror&lt;/code&gt;. First, clone your &amp;quot;source&amp;quot; repository:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone --mirror git://gitorious.org/weasyprint/weasyprint.git weasyprint
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;--mirror&lt;/code&gt; implies &lt;code&gt;--bare&lt;/code&gt;. This repository is not for working, you
don&#039;t want it to have a working directory. More importantly, &lt;code&gt;--mirror&lt;/code&gt;
sets up the origin remote so that git fetch will directly fetch into
local branches without doing any merge. It will force the update if
the remote history has diverged from the local one.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git fetch
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now our local repository is an exact mirror of what we have on
&lt;a href=&quot;http://gitorious.com/&quot;&gt;Gitorious&lt;/a&gt;. Let&#039;s push it to &lt;a href=&quot;http://github.com/&quot;&gt;GitHub&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git remote add github git@github.com:SimonSapin/WeasyPrint.git
git push --mirror github
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;--mirror&lt;/code&gt; option for git push is similar to that for git clone:
instead of pushing just a branch, it says that all references (branches,
tags, -) should be the same on the remote end as they are here, even if
it means forced updates or removing. Now our
&lt;a href=&quot;http://gitorious.com/&quot;&gt;GitHub&lt;/a&gt; repository also is a mirror. Let&#039;s
update it every hour with cron. The &lt;code&gt;-q&lt;/code&gt; option says to suppress normal
output but keep error messages, which cron should send you by email if
your server is properly configured.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;42 *    * * *   cd /path/to/weasyprint &amp;amp;&amp;amp; git fetch -q &amp;amp;&amp;amp; git push -q --mirror github
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;Warning%3A+--mirror+is+like+--force&quot; name=&quot;Warning%3A+--mirror+is+like+--force&quot;&gt;Warning: &lt;code&gt;--mirror&lt;/code&gt; is like &lt;code&gt;--force&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Both &lt;code&gt;--mirror&lt;/code&gt; options are kind of like &lt;code&gt;--force&lt;/code&gt; in that you can
lose data if you&#039;re not careful. It will make exact mirrors, no question
asked. If you push changes to the mirror&#039;s destination, they will be
overwritten/removed on the next update if they are not in the mirror&#039;s
source.&lt;/p&gt;
&lt;p&gt;Original article by Simon Sapin: &lt;a href=&quot;http://exyr.org/2011/git-mirrors/&quot;&gt;http://exyr.org/2011/git-mirrors/&lt;/a&gt;&lt;/p&gt;</content>
</entry>
<entry>
<title>Kerberos howtos</title>
<link href="https://www.0ink.net/posts/2013/2013-05-27-kerberos-howtos.html"></link>
<id>urn:uuid:72e9aa49-6b5f-bd00-ecd4-2dd8f18b1999</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Kerberos is a network authentication protocol which works on the basis
of &quot;tickets&quot; to allow nodes communicating over a non-secure network to
prove their identity to one another in a secure manner. (Source
Kerberos_(protocol) )
Backups
Create backup:
...]]></summary>
<content type="html">&lt;p&gt;Kerberos is a network authentication protocol which works on the basis
of &amp;quot;tickets&amp;quot; to allow nodes communicating over a non-secure network to
prove their identity to one another in a secure manner. (Source
&lt;a href=&quot;http://en.wikipedia.org/wiki/Kerberos_(protocol)&quot;&gt;Kerberos_(protocol)&lt;/a&gt; )&lt;/p&gt;
&lt;h2 id=&quot;Backups&quot; name=&quot;Backups&quot;&gt;Backups&lt;/h2&gt;
&lt;p&gt;Create backup:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;kdb5_util dump _dump_file_
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Restore from dump file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;kdb5_util load _dump_file_
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Master%2FSlave+replication&quot; name=&quot;Master%2FSlave+replication&quot;&gt;Master/Slave replication&lt;/h2&gt;
&lt;p&gt;Initial set-up:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(master)# kdb5_util dump _dump_file_
(master)# kprop -d -f _dump_file_ _slave_
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In &lt;code&gt;crontab&lt;/code&gt; on master:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;krb5_util dump _dump_file_
kprop -f _dump_file_ _slave_
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;kadmin+command&quot; name=&quot;kadmin+command&quot;&gt;kadmin command&lt;/h2&gt;
&lt;p&gt;From command line:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;kadmin.local -q &#039;cmd&#039;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;listprincs - list principals&lt;/li&gt;
&lt;li&gt;ank &lt;em&gt;principal&lt;/em&gt; - new principal (input: password)&lt;/li&gt;
&lt;li&gt;delprinc &lt;em&gt;principal&lt;/em&gt; - delete principal (input: yes/no)&lt;/li&gt;
&lt;li&gt;ank -randkey host/&lt;em&gt;fqdn&lt;/em&gt;@REALM - create a service key&lt;/li&gt;
&lt;li&gt;ktadd -k &lt;em&gt;filename&lt;/em&gt; host/&lt;em&gt;fqdn&lt;/em&gt;@REALM export key to keytab file.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Save keytab in &lt;code&gt;/etc/krb5.keytab&lt;/code&gt; and&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;chown root:root /etc/krb5.keytab
chmod 400 /etc/krb5.keytab
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Logging&quot; name=&quot;Logging&quot;&gt;Logging&lt;/h2&gt;
&lt;p&gt;To turn logging on, add this section to &lt;code&gt;/etc/krb5.conf&lt;/code&gt; (adapt the
file paths to your likings):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; [logging]
     default = FILE:/var/log/krb5.log
     kdc = FILE:/var/log/krb5kdc.log
     admin_server = FILE:/var/log/kadmind.log
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Merging+%28or+editing%29+a+keytab+file&quot; name=&quot;Merging+%28or+editing%29+a+keytab+file&quot;&gt;Merging (or editing) a keytab file&lt;/h2&gt;
&lt;p&gt;Merging or editing keytabs is done through the &lt;strong&gt;ktutil&lt;/strong&gt; command.
Suppose we have two keytabs, keytab1 and keytab2, each having their own
set of keys, and we would like to merge the two keytabs in one (or
create a new keytab containing specific keys). The operation is done
through the &lt;strong&gt;ktutil&lt;/strong&gt; shell, with &lt;strong&gt;rkt&lt;/strong&gt; and &lt;strong&gt;write_kt&lt;/strong&gt; commands,
and optionally &lt;strong&gt;delent&lt;/strong&gt; if you want to delete some entities. Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; # ktutil
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read content of keytab1:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; ktutil: rkt keytab1
 ktutil: list
 slot KVNO Principal
 ---- ---- -------------------------------------------------------------
 1 3 &amp;lt;principal and key of keytab1&amp;gt;
 2 3 &amp;lt;principal and key of keytab1&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, we will read the content of keytab2:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; ktutil: rkt keytab2
 ktutil: list
 slot KVNO Principal
 ---- ---- -----------------------------------------------------------
 1 3 &amp;lt;principal and key of keytab1&amp;gt;
 2 3 &amp;lt;principal and key of keytab1&amp;gt;
 3 2 &amp;lt;principal and key of keytab2&amp;gt;
 4 2 &amp;lt;principal and key of keytab2&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Save this content in a temporary keytab:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; ktutil: write_kt /tmp/krb5.keytab
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This utility is used to duplicate and tweak keytab entries (as its
name implies), and remove the need of exporting the keys out of the KDC
twice or more (simultaneously avoiding KVNO&#039;s increment).&lt;/p&gt;
&lt;h2 id=&quot;OpenWRT+recipes&quot; name=&quot;OpenWRT+recipes&quot;&gt;OpenWRT recipes&lt;/h2&gt;
&lt;h3 id=&quot;Packages&quot; name=&quot;Packages&quot;&gt;Packages&lt;/h3&gt;
&lt;h4 id=&quot;Server&quot; name=&quot;Server&quot;&gt;Server&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;krb5-server&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;krb5-libs&lt;/code&gt; (dependency of &lt;strong&gt;krb5-server&lt;/strong&gt;)&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;Client&quot; name=&quot;Client&quot;&gt;Client&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;krb5-client&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Configuration&quot; name=&quot;Configuration&quot;&gt;Configuration&lt;/h3&gt;
&lt;p&gt;Create the file &lt;code&gt;/etc/krb5.conf&lt;/code&gt; with the following credentials. Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[libdefaults]
    default_realm = YOURDOMAIN.ORG
    dns_lookup_realm = false
    dns_lookup_kdc = false
    ticket_lifetime = 24h
    forwardable = yes

[realms]
    YOURDOMAIN.ORG = {
        kdc = server_address_of_this_machine:88
        admin_server = server_address_of_this_machine:749
        default_domain = yourdomain.org
    }

[domain_realm]
    .yourdomain.org = YOURDOMAIN.ORG
    yourdomain.org = YOURDOMAIN.ORG
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Replace &lt;code&gt;YOURDOMAIN.ORG&lt;/code&gt; / &lt;code&gt;yourdomain.org&lt;/code&gt; with the domain name of
your domain the server should act for (names must be specified in
UPPER- / lowercase as shown above). Replace &lt;code&gt;server_address_of_this_machine&lt;/code&gt;
with the host name/IP adress of this server you&#039;re setting up.&lt;/p&gt;
&lt;h4 id=&quot;Starting+the+server&quot; name=&quot;Starting+the+server&quot;&gt;Starting the server&lt;/h4&gt;
&lt;p&gt;Start the server by issuing&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/etc/init.d/krb5kdc start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This should create the &lt;code&gt;/etc/krb5kdc/&lt;/code&gt; directory with the following files&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-rw-------    1 root     root         8192 Feb 13 11:17 principal
-rw-------    1 root     root         8192 Feb 13 09:12 principal.kadm5
-rw-------    1 root     root            0 Feb 13 09:12 principal.kadm5.lock
-rw-------    1 root     root            0 Feb 13 11:17 principal.ok
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In case you don&#039;t get any error messages check your server by logging
on with &lt;code&gt;kadmin.local&lt;/code&gt; In case everything works well you will see the
following message&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;root@bridge:~# kadmin.local
Authenticating as principal xxxxxxx/admin@YOURDOMAIN.ORG with password.
kadmin.local:
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;Start+on+boot&quot; name=&quot;Start+on+boot&quot;&gt;Start on boot&lt;/h4&gt;
&lt;p&gt;To enable/disable automatic start on boot:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/etc/init.d/krb5kdc enable
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;this simply creates a symlink: &lt;code&gt;/etc/rc.d/S60krb5kdc&lt;/code&gt; ? &lt;code&gt;/etc/init.d/krb5kdc&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/etc/init.d/krb5kdc disable
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;this removes the symlink again&lt;/p&gt;
&lt;h2 id=&quot;References&quot; name=&quot;References&quot;&gt;References&lt;/h2&gt;
&lt;p&gt;See Also:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.kerberos.org/software/adminkerberos.pdf&quot;&gt;http://www.kerberos.org/software/adminkerberos.pdf&lt;/a&gt;&lt;br /&gt;
Refer to pages 16/17 for testing procedures.&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Emacs Cheat Sheet</title>
<link href="https://www.0ink.net/posts/2013/2013-05-27-emacs-cheat-sheet.html"></link>
<id>urn:uuid:8cfd6fc8-762c-de92-5da9-4610e243094b</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Quick reference article for how to use Emacs.  Yes, it is really
old skool!
Cursor Motion



...]]></summary>
<content type="html">&lt;p&gt;Quick reference article for how to use Emacs.  Yes, it is really
old skool!&lt;/p&gt;
&lt;h2 id=&quot;Cursor+Motion&quot; name=&quot;Cursor+Motion&quot;&gt;Cursor Motion&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Cursor Motion&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;C-f&lt;/td&gt;
&lt;td&gt;Forward one character&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-b&lt;/td&gt;
&lt;td&gt;Backward one character&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-n&lt;/td&gt;
&lt;td&gt;Next line&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-p&lt;/td&gt;
&lt;td&gt;Previous line&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-a&lt;/td&gt;
&lt;td&gt;Beginning of line&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-e&lt;/td&gt;
&lt;td&gt;End of line&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-v&lt;/td&gt;
&lt;td&gt;Next screenful&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M-v&lt;/td&gt;
&lt;td&gt;Previous screenful&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M-&amp;lt;&lt;/td&gt;
&lt;td&gt;Beginning of buffer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M-&amp;gt;&lt;/td&gt;
&lt;td&gt;End of buffer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-s&lt;/td&gt;
&lt;td&gt;Search forward incrementally&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-r&lt;/td&gt;
&lt;td&gt;Reverse search incrementally&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-u C-s&lt;/td&gt;
&lt;td&gt;Reg-Exp Search forward incrementally&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-u C-r&lt;/td&gt;
&lt;td&gt;Reg-Exp Reverse search incrementally&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-x C-x&lt;/td&gt;
&lt;td&gt;Swap mark and cursor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-Space&lt;/td&gt;
&lt;td&gt;Set mark&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-l&lt;/td&gt;
&lt;td&gt;Cursor to the middle of the screen&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M-}&lt;/td&gt;
&lt;td&gt;Forward one paragraph&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M-{&lt;/td&gt;
&lt;td&gt;Backward one paragraph&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;Text+editing&quot; name=&quot;Text+editing&quot;&gt;Text editing&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Editing&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;C-q&lt;/td&gt;
&lt;td&gt;Literal command&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-d&lt;/td&gt;
&lt;td&gt;Delete next character&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backspc&lt;/td&gt;
&lt;td&gt;Delete previous character&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M-%&lt;/td&gt;
&lt;td&gt;Query string replacement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M-d&lt;/td&gt;
&lt;td&gt;Delete next word&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M-Bcksp&lt;/td&gt;
&lt;td&gt;Delete previous word&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-k&lt;/td&gt;
&lt;td&gt;Kill to end of line (delete to end of line)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-w&lt;/td&gt;
&lt;td&gt;Cut region&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M-w&lt;/td&gt;
&lt;td&gt;Copy region&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-y&lt;/td&gt;
&lt;td&gt;Yank most recent cut/copy (paste command)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M-y&lt;/td&gt;
&lt;td&gt;Replace yanked text with previously cut/copy text (only works immediatly after C-y or another M-y)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-x u&lt;/td&gt;
&lt;td&gt;undo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-u ##&lt;/td&gt;
&lt;td&gt;Repeat the next command&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;File+Commands&quot; name=&quot;File+Commands&quot;&gt;File Commands&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Files&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;C-x C-f&lt;/td&gt;
&lt;td&gt;Open a file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-x C-s&lt;/td&gt;
&lt;td&gt;Save buffer to file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-x C-w&lt;/td&gt;
&lt;td&gt;Write buffer to file (Save As)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-x C-c&lt;/td&gt;
&lt;td&gt;Exit Emacs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-x s&lt;/td&gt;
&lt;td&gt;Save all buffers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-x i&lt;/td&gt;
&lt;td&gt;Insert file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-g&lt;/td&gt;
&lt;td&gt;Cancel current command&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-z&lt;/td&gt;
&lt;td&gt;Suspend/Minimize Emacs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;Buffers&quot; name=&quot;Buffers&quot;&gt;Buffers&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Buffers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;C-x b&lt;/td&gt;
&lt;td&gt;Switch to buffer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-x 1&lt;/td&gt;
&lt;td&gt;Close all other buffers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-x 2&lt;/td&gt;
&lt;td&gt;Split current buffer in tow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-x 3&lt;/td&gt;
&lt;td&gt;Split current buffer horizontally&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-x 0&lt;/td&gt;
&lt;td&gt;Close current buffer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-x o&lt;/td&gt;
&lt;td&gt;Switch to other buffer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-x C-b&lt;/td&gt;
&lt;td&gt;List buffers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-x k&lt;/td&gt;
&lt;td&gt;Kill buffer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-x ^&lt;/td&gt;
&lt;td&gt;Grow window vertically; prefix is number of lines&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;Help&quot; name=&quot;Help&quot;&gt;Help&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Help&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;C-h C-h&lt;/td&gt;
&lt;td&gt;Help menu&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-h i&lt;/td&gt;
&lt;td&gt;Info&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-h a&lt;/td&gt;
&lt;td&gt;Apropos&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-h b&lt;/td&gt;
&lt;td&gt;Key bindings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-h m&lt;/td&gt;
&lt;td&gt;Mode help&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-h k&lt;/td&gt;
&lt;td&gt;Show command documentation; prompts for keystrokes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-h c&lt;/td&gt;
&lt;td&gt;Show command name on message line; prompts for keystrokes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-h f&lt;/td&gt;
&lt;td&gt;Describe function; prompts for command or function name, shows documentation in other window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-h i&lt;/td&gt;
&lt;td&gt;Info browser; gives access to online documentation for emacs and more&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;Misc&quot; name=&quot;Misc&quot;&gt;Misc&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Other&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;M-/&lt;/td&gt;
&lt;td&gt;Abbreviation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M-q&lt;/td&gt;
&lt;td&gt;Autoformat current text region&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-M-&lt;/td&gt;
&lt;td&gt;Re-indent current region&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-x (&lt;/td&gt;
&lt;td&gt;Start defining macro&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-x )&lt;/td&gt;
&lt;td&gt;Stop macro defintion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-x e&lt;/td&gt;
&lt;td&gt;Execute macro&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;C-Mode+Commands&quot; name=&quot;C-Mode+Commands&quot;&gt;C-Mode Commands&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;C-Mode&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;C-j&lt;/td&gt;
&lt;td&gt;Insert a newline and indent the next line.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-c C-q&lt;/td&gt;
&lt;td&gt;Fix indentation of current function&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-c C-a&lt;/td&gt;
&lt;td&gt;Toggle the auto-newline-insertion mode. (If it was off, it will now be on and vice versa.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-c C-d&lt;/td&gt;
&lt;td&gt;Toggle the hungry delete mode&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;Extended+commands&quot; name=&quot;Extended+commands&quot;&gt;Extended commands&lt;/h2&gt;
&lt;p&gt;Enter &lt;code&gt;ESC&lt;/code&gt; + &lt;code&gt;[&lt;/code&gt; and enter this text:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;M-x&lt;/th&gt;
&lt;th&gt;Commands&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;c-set-style&lt;/td&gt;
&lt;td&gt;Change the indentation style&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;replace-string&lt;/td&gt;
&lt;td&gt;Global string replacement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;revert-buffer&lt;/td&gt;
&lt;td&gt;Throw out all changes and revert to the last saved version of the file.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gdb&lt;/td&gt;
&lt;td&gt;Start GNU debugger&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;shell&lt;/td&gt;
&lt;td&gt;Start shell in new buffer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;print-buffer&lt;/td&gt;
&lt;td&gt;Send the contents of the current buffer to the printer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;compile&lt;/td&gt;
&lt;td&gt;Compile a program&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;set-variable&lt;/td&gt;
&lt;td&gt;Change the value of an Emacs variable to customize Emacs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;artist-mode&lt;/td&gt;
&lt;td&gt;Start artist mode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;artist-mode-off&lt;/td&gt;
&lt;td&gt;Exit artist mode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tabify&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;untabify&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;Tags&quot; name=&quot;Tags&quot;&gt;Tags&lt;/h2&gt;
&lt;p&gt;Use tags to navigate source code. It&#039;s not hard to set up. This takes advantage of a popular tool called &amp;quot;Exuberant Ctags&amp;quot; (AKA ctags, or etags) that scans your source code and indexes the symbols into a &lt;code&gt;TAGS&lt;/code&gt; file. Note: emacs comes with a tool called &amp;quot;etags&amp;quot; that does almost the same thing as Exuberant Ctags. In cygwin, the &amp;quot;etags&amp;quot; binary is actually Exuberant Ctags. Confused yet? My advice is, ignore Emacs etags, and use Exuberant Ctags, whatever it happens to be called in your part of the universe. To generate a &lt;code&gt;TAGS&lt;/code&gt; file, do this in the root of your code tree (stick this in a script or Makefile):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#ETAGS=/cygdrive/c/emacs-21.3/bin/etags.exe
ETAGS=etags # Exuberant ctags
rm TAGS
find . -name &#039;*.cpp&#039; -o -name &#039;*.h&#039; -o -name &#039;*.c&#039; -print0 
     | xargs $(ETAGS) --extra=+q --fields=+fksaiS --c++-kinds=+px --append
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, when you&#039;re reading code and want to see the definition(s) of a symbol:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;M-.&lt;/code&gt;: goes to the symbol definition&lt;/li&gt;
&lt;li&gt;&lt;code&gt;M-0 M-.&lt;/code&gt;: goes to the next matching definition&lt;/li&gt;
&lt;li&gt;&lt;code&gt;M-*&lt;/code&gt;: return to your starting point&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>ArchLinux tips</title>
<link href="https://www.0ink.net/posts/2013/2013-05-27-archlinux-tips.html"></link>
<id>urn:uuid:6c83148e-383c-b9f3-1b4f-bde904d37690</id>
<updated>2022-03-03T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[A bunch of recipes useful for an ArchLinux system environment.
Mostly around system administration.
Custom Repos and Packages
In the repo directory, put all the packages in there.
repo-add ./custom.db.tar.gz ./*

...]]></summary>
<content type="html">&lt;p&gt;A bunch of recipes useful for an ArchLinux system environment.&lt;/p&gt;
&lt;p&gt;Mostly around system administration.&lt;/p&gt;
&lt;h2 id=&quot;Custom+Repos+and+Packages&quot; name=&quot;Custom+Repos+and+Packages&quot;&gt;Custom Repos and Packages&lt;/h2&gt;
&lt;p&gt;In the repo directory, put all the packages in there.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;repo-add ./custom.db.tar.gz ./*
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add to &lt;code&gt;pacman.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[custom]
SigLevel = [Package|Databse]Never|Optional|Required
Server = path-to-repo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See also &lt;code&gt;repo-remove&lt;/code&gt;. A package database is a tar file, optionally compressed. Valid extensions are &lt;code&gt;.db&lt;/code&gt; or &lt;code&gt;.files&lt;/code&gt; followed by an archive extension of &lt;code&gt;.tar&lt;/code&gt;, &lt;code&gt;.tar.gz&lt;/code&gt;, &lt;code&gt;.tar.bz2&lt;/code&gt;, &lt;code&gt;.tar.xz&lt;/code&gt;, or &lt;code&gt;.tar.Z&lt;/code&gt;. The file does not need to exist, but all parent directories must exist. ?Can we create a &lt;code&gt;rpmgot.php&lt;/code&gt; hack?&lt;/p&gt;
&lt;h2 id=&quot;Safe+automatic+pacman+upgrades&quot; name=&quot;Safe+automatic+pacman+upgrades&quot;&gt;Safe automatic pacman upgrades&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://bbs.archlinux.org/viewtopic.php?id=66822&quot;&gt;safepac&lt;/a&gt; : This is an approach for automating pacman upgrades yet catching &lt;em&gt;problematic&lt;/em&gt; updates before hand.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Building+packages&quot; name=&quot;Building+packages&quot;&gt;Building packages&lt;/h2&gt;
&lt;p&gt;requires: @base-devel, abs, fakeroot&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;makepkg -s 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;makeworld ?
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Working+with+the+serial+console&quot; name=&quot;Working+with+the+serial+console&quot;&gt;Working with the serial console&lt;/h2&gt;
&lt;p&gt;Configure your Arch Linux machine so you can connect to it via the serial console port (com port). This will enable you to administer the machine even if it has no keyboard, mouse, monitor, or network attached to it (a headless server).&lt;/p&gt;
&lt;h3 id=&quot;Configuration&quot; name=&quot;Configuration&quot;&gt;Configuration&lt;/h3&gt;
&lt;p&gt;Add this to the bootloader kernel line:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;console=tty0 console=ttyS0,9600
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From systemd:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl enable getty@ttyS0.service 
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Installing+Arch+Linux+using+the+serial+console&quot; name=&quot;Installing+Arch+Linux+using+the+serial+console&quot;&gt;Installing Arch Linux using the serial console&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Boot the target machine using the Arch Linux installation CD.&lt;/li&gt;
&lt;li&gt;When the bootloader appears, select &amp;quot;Boot Arch Linux ()&amp;quot; and press tab to edit&lt;/li&gt;
&lt;li&gt;Append console=ttyS0 and press enter&lt;/li&gt;
&lt;li&gt;Systemd should now detect ttyS0 and spawn a serial getty on it, allowing you to proceed as usual&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Note: After setup is complete, the console settings will not be saved on the target machine; in order to avoid having to connect a keyboard and monitor, configure console access on the target machine before rebooting.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;Identifying+files+not+owned+by+any+package&quot; name=&quot;Identifying+files+not+owned+by+any+package&quot;&gt;Identifying files not owned by any package&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;pacman-disowned

#!/bin/sh

tmp=${TMPDIR-/tmp}/pacman-disowned-$UID-$$
db=$tmp/db
fs=$tmp/fs

mkdir &quot;$tmp&quot;
trap &#039;rm -rf &quot;$tmp&quot;&#039; EXIT

pacman -Qlq | sort -u &amp;gt; &quot;$db&quot;

find /bin /etc /sbin /usr 
  ! -name lost+found 
  ( -type d -printf &#039;%p/n&#039; -o -print ) | sort &amp;gt; &quot;$fs&quot;

comm -23 &quot;$fs&quot; &quot;$db&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Pacman+one+liners&quot; name=&quot;Pacman+one+liners&quot;&gt;Pacman one liners&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Remove packages and its dependancies. pacman -Rs ...&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;List explicitly installed packages pacman -Qeq&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;List orphans pacman -Qtdq&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Remove everything but base group pacman -Rs $(comm -23 &amp;lt;(pacman -Qeq|sort) &amp;lt;((for i in $(pacman -Qqg base); do pactree -ul $i; done)|sort -u|cut -d &#039; &#039; -f 1))&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Listing changed configuraiton files pacman -Qii | awk &#039;/^MODIFIED/ {print $2}&#039;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Download a package without installing it pacman -Sw package_name&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Manage pacman cache&lt;/p&gt;
&lt;p&gt;paccache -h&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>PHP notes</title>
<link href="https://www.0ink.net/posts/2013/2013-05-23-php-coding-style.html"></link>
<id>urn:uuid:e362d2f6-48e0-fb7f-3dee-12ddeb25d079</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Notes on doing different things within the PHP language.
Object oriented introspection

property_exists(obj,prop_name)
method_exists(obj,method_name)
is_a(obj,'clas_name') or ($obj instanceof ClassName)
...]]></summary>
<content type="html">&lt;p&gt;Notes on doing different things within the PHP language.&lt;/p&gt;
&lt;h2 id=&quot;Object+oriented+introspection&quot; name=&quot;Object+oriented+introspection&quot;&gt;Object oriented introspection&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;property_exists(obj,prop_name)&lt;/li&gt;
&lt;li&gt;method_exists(obj,method_name)&lt;/li&gt;
&lt;li&gt;is_a(obj,&#039;clas_name&#039;) or ($obj instanceof ClassName)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Dynamic+coding&quot; name=&quot;Dynamic+coding&quot;&gt;Dynamic coding&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Call a method: call_user_func(array($obj,&#039;method&#039;,...args...)&lt;/li&gt;
&lt;li&gt;You can simply $obj-&amp;gt;prop = value to add properties.&lt;/li&gt;
&lt;li&gt;or you can use __set and __get. See &lt;a href=&quot;http://php.net/manual/en/language.oop5.overloading.php&quot;&gt;http://php.net/manual/en/language.oop5.overloading.php&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;varargs&quot; name=&quot;varargs&quot;&gt;varargs&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://php.net/manual/en/function.func-get-arg.php&quot;&gt;func_get_arg(num)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.php.net/manual/en/function.func-get-args.php&quot;&gt;func_get_args()&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.php.net/manual/en/function.func-num-args.php&quot;&gt;func_get_num_args()&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>Git recipes</title>
<link href="https://www.0ink.net/posts/2013/2013-05-23-git-recipes.html"></link>
<id>urn:uuid:95988ead-451a-30e1-2f49-36152603ac9e</id>
<updated>2025-01-01T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[
Sharing repositories
Rewriting history

Rolling back the last commit

...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Sharing+repositories&quot;&gt;Sharing repositories&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Rewriting+history&quot;&gt;Rewriting history&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Rolling+back+the+last+commit&quot;&gt;Rolling back the last commit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Restoring+changes&quot;&gt;Restoring changes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#User+friendly+version+ids&quot;&gt;User friendly version ids&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Branches&quot;&gt;Branches&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Tagging&quot;&gt;Tagging&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Creating+tags&quot;&gt;Creating tags&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Temporary+snapshots&quot;&gt;Temporary snapshots&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Sharing+tags&quot;&gt;Sharing tags&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#To+pull+tags+%28if+there+aren%27t+any%29&quot;&gt;To pull tags (if there aren&#039;t any)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Deleting+tags&quot;&gt;Deleting tags&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Rename+a+tag%3A&quot;&gt;Rename a tag:&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Setting+up+GIT&quot;&gt;Setting up GIT&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Using+%7E%2F.netrc+for+persistent+authentication&quot;&gt;Using ~/.netrc for persistent authentication&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Creating+new+repositories&quot;&gt;Creating new repositories&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Vendor+Branches&quot;&gt;Vendor Branches&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#GIT+through+patches&quot;&gt;GIT through patches&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Maintenance&quot;&gt;Maintenance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Submodules&quot;&gt;Submodules&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#setting+git+email+per+repository&quot;&gt;setting git email per repository&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;A collection of small useful recipes for using with &lt;code&gt;Git&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;Sharing+repositories&quot; name=&quot;Sharing+repositories&quot;&gt;Sharing repositories&lt;/h2&gt;
&lt;p&gt;Use the command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git init --bare --shared /srv/git/myrepo.git&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Creates a repo with the following config:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;core.bare = true&lt;/code&gt;: make it a bare repo&lt;/li&gt;
&lt;li&gt;&lt;code&gt;core.sharedrepository = 1&lt;/code&gt; (same as &lt;code&gt;core.sharedrepository = group&lt;/code&gt;): the repo
directory and all directories later created in it will be managed by git to allow
group read, write, and execute permissions (with the sgid bit set as well -- so as
to work with users different primary group)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;receive.denyNonFastforwards = 1&lt;/code&gt;: deny non fast-forward pushes to the repo&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Make sure that the user and group ownership of the files is correct.  You may need to
run &lt;code&gt;chown -R&lt;/code&gt; or &lt;code&gt;chgrp -R&lt;/code&gt; to correct this.&lt;/p&gt;
&lt;p&gt;If you want to fine-tune the user, group, or other users&#039; permissions, use
&lt;code&gt;--shared=0NNN&lt;/code&gt;, where &lt;code&gt;NNN&lt;/code&gt; are the standard user, group, and other bits for files
(the execute and sgid bits on directories will be managed appropriately by git). For
example, this allows read and write access to the user, and read-only access to the
group (and no access to other):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git init --bare --shared=0640 /srv/git/myrepo.git&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This allows read and write access to the user and group (and no access to other):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git init --bare --shared=0660 /srv/git/myrepo.git&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This allows read and write access to the user and group, and read-only access to other:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git init --bare --shared=0664 /srv/git/myrepo.git&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the default.&lt;/p&gt;
&lt;h2 id=&quot;Rewriting+history&quot; name=&quot;Rewriting+history&quot;&gt;Rewriting history&lt;/h2&gt;
&lt;h3 id=&quot;Rolling+back+the+last+commit&quot; name=&quot;Rolling+back+the+last+commit&quot;&gt;Rolling back the last commit&lt;/h3&gt;
&lt;p&gt;if nobody has pulled your remote repo yet, you can change your branch HEAD and force push it to said remote repo:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git reset --hard HEAD^
git push -f&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Restoring+changes&quot; name=&quot;Restoring+changes&quot;&gt;Restoring changes&lt;/h2&gt;
&lt;p&gt;So in the event that you want to go back to a previous version of a file. First you must identify the version using:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git log $file&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once you know which commit to go to, do:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git checkout $hash $file&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git commit $file&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;User+friendly+version+ids&quot; name=&quot;User+friendly+version+ids&quot;&gt;User friendly version ids&lt;/h2&gt;
&lt;p&gt;Creating version ids Use:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; git describe&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Gives:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; $tag-$commit_count-$hash&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However for this to work, you need to have a good tag set and a good tag naming convention.&lt;/p&gt;
&lt;h2 id=&quot;Branches&quot; name=&quot;Branches&quot;&gt;Branches&lt;/h2&gt;
&lt;p&gt;Main branch names:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;master - The main branch. Source code of HEAD always reflects production-ready status.&lt;/li&gt;
&lt;li&gt;develop or dev - Main dev branch. HEAD always reflects state with the latest development changes for the next release. This can sometimes be called the &amp;quot;integration branch&amp;quot; and used to generate automatic nightly builds.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Also a variety of supporting branches to aid parallel development between team members, ease tracking of features, prepare for production releases and to assist in quickly fixing live production problems. Unlike the main branches, these branches always have a limited life time, since they will be removed eventually. Creating a new branch:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; git checkout -b new_branch develop
 # Creates a branch called &quot;new_branch&quot; from &quot;develop&quot; and switches to it
  git push -u origin new_branch
  # Pushes &quot;new_branch&quot; to the remote repo&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Listing branches&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; git branch      # List all local branches
 git branch -a  # List local and remote branches&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Merging branches&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; git checkout dev
 # Switches to branch that will receive the commits...
 git merge --no-ff &quot;feature_branch&quot;
 # makes the a single commit (instead of replaying all the commits from the feature branch)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Deleting branches&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git branch -d branch_name    # Only local branches
git push origin --delete branch_name # Remote branch
git push origin :branch_name # Old format for deleting... prefix with &quot;:&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Clean-up delete branches in remote repo from local repo...&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git branch --delete branch
git remote prune origin&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Tagging&quot; name=&quot;Tagging&quot;&gt;Tagging&lt;/h2&gt;
&lt;h3 id=&quot;Creating+tags&quot; name=&quot;Creating+tags&quot;&gt;Creating tags&lt;/h3&gt;
&lt;p&gt;Tag releases with&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git tag -a $tagname -m &quot;$descr&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This creates an annotated tag that has full meta data content and it is favored by Git describe.&lt;/p&gt;
&lt;h3 id=&quot;Temporary+snapshots&quot; name=&quot;Temporary+snapshots&quot;&gt;Temporary snapshots&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;git tag $tagname&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These are lightweight tag that are associated to a specific commit.&lt;/p&gt;
&lt;h3 id=&quot;Sharing+tags&quot; name=&quot;Sharing+tags&quot;&gt;Sharing tags&lt;/h3&gt;
&lt;p&gt;By default are not pushed. They need to be exported with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git push origin $tagname&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git push origin --tags&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;To+pull+tags+%28if+there+aren%27t+any%29&quot; name=&quot;To+pull+tags+%28if+there+aren%27t+any%29&quot;&gt;To pull tags (if there aren&#039;t any)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;git fetch --tags&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Deleting+tags&quot; name=&quot;Deleting+tags&quot;&gt;Deleting tags&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;git tag -d $tagname    # Local tags
git push --delete origin $tagname # Remote tags
git push origin :refs/tags/$tagname   # Remote tags (OLD VERSION)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Rename+a+tag%3A&quot; name=&quot;Rename+a+tag%3A&quot;&gt;Rename a tag:&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;git tag new old
git tag -d old
git push origin :refs/tags/old&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Setting+up+GIT&quot; name=&quot;Setting+up+GIT&quot;&gt;Setting up GIT&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;git config --global user.name &quot;user&quot;
git config --global user.email &quot;email&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Other settings:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[http]
  sslVerify = false
  proxy = http://10.47.142.30:8080/
[user]
  email = alejandro_liu@hotmail.com
  name = alex&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Using+%7E%2F.netrc+for+persistent+authentication&quot; name=&quot;Using+%7E%2F.netrc+for+persistent+authentication&quot;&gt;Using ~/.netrc for persistent authentication&lt;/h3&gt;
&lt;p&gt;Create a file called &lt;code&gt;.netrc&lt;/code&gt; in your home directory. Make sure you sets permissions &lt;code&gt;600&lt;/code&gt; so that it is only readable by user. With Windows, create a file &lt;code&gt;_netrc&lt;/code&gt; in your home directory. You may need to define a %HOME% environment variable. In Windows 7 you can use:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;setx HOME %USERPROFILE%&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;set HOME=%HOMEDRIVE%%HOMEPATH%&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The contents of &lt;code&gt;.netrc&lt;/code&gt; (or &lt;code&gt;_netrc&lt;/code&gt;) are as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;|machine $system
|   login $user
|   password $pwd
|machine $system
|   login $user
|   password $pwd&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Creating+new+repositories&quot; name=&quot;Creating+new+repositories&quot;&gt;Creating new repositories&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;mkdir ~/hello-world
cd ~/hello-world
git init
# Creates an empty repository in ~/hello-world
touch file
git add file
git commit -m &#039;first commit&#039;
# Creates a new file and commits locally
git remote add origin &#039;https://$user:$passwd@github.com/$user/hello-world.git
# Creates a remote name for push/pull
git push origin master
# Send commits to remote&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Creating a bare repo:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir templ
cd templ
echo &quot;Initial commit&quot; &amp;amp;gt; README.md
git add README.md
git commit -m&quot;Initial commit&quot;
git clone --bare .&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Vendor+Branches&quot; name=&quot;Vendor+Branches&quot;&gt;Vendor Branches&lt;/h2&gt;
&lt;p&gt;Set-up&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;unzip wordpress-2.3.zip
cd wordpress
# Note, unzip creates this directory...
git init
git add .
git commit -m &#039;Import wordpress 2.3&#039;
git tag v2.3
git branch upstream
# Create the upstream branch used to track new vendor releases&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When a new release comes out:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd wordpress
git checkout upstream
rm -r \*
# Delete all files in the main directory but doesn&#039;t touch dot files (like .git)
(cd .. &amp;amp;amp;&amp;amp;amp; unzip wordpress-2.3.1.zip)
git add .
git commit -a -m &#039;Import wordpress 2.3.1&#039;
git tag v2.3.1
git checkout master
git merge upstream&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A variation of vendor branches is to sync with an upstream fork in github. Read this guide on how to do that: &lt;a href=&quot;https://help.github.com/articles/syncing-a-fork/&quot;&gt;Syncing a fork on github&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;GIT+through+patches&quot; name=&quot;GIT+through+patches&quot;&gt;GIT through patches&lt;/h2&gt;
&lt;p&gt;Creating a patch:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; ... prepare a new branch to keep work separate ...
 git checkout -b mybranch
 ... do work ...
 git commit -a
 .. create the patch from branch &quot;master&quot;...
 git format-patch master --stdout &amp;amp;gt; file.patch&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To apply patch..&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; ... show what the patch file will do ...
 git apply --stat file.patch
 .. displays issues the patch might cause...
 git apply --check file.patch
 .. apply with am (so you can sign-off)
 git am --signoff &amp;amp;lt; file.patch&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Maintenance&quot; name=&quot;Maintenance&quot;&gt;Maintenance&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;git fsck
git gc --prune=now     # Clean-up
git remote prune origin # Clean-up stale references to deleted remote objects&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Submodules&quot; name=&quot;Submodules&quot;&gt;Submodules&lt;/h2&gt;
&lt;p&gt;Add submodules to a project:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git submodule add $repo_url $dir&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Clone a project with submodules:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone $repo_url
cd $repo
git submodule init
git submodule update&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or in a single command (Git &amp;gt;1.6.5):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone --recursive $repo_url&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For already cloned (Git &amp;gt;1.6.5):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone $repo_url
cd $repo
git submodule update --init --recursive&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To keep a submodule up-to-date:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git pull
git submodule update&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Remove sub-modules:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git submodule deinit $submodule
git rm $submodule # No trailing slash!&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;setting+git+email+per+repository&quot; name=&quot;setting+git+email+per+repository&quot;&gt;setting git email per repository&lt;/h2&gt;
&lt;p&gt;Navigate to the work repository, then at the root folder run the
following command to change the email.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git config --local user.email name@work.com&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; this command only affects the current repository. Any
other repositories will still use the default email specified in
&lt;code&gt;~/.gitconfig&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Alternatively, you can have different configurations based on a
directory path by using:&lt;/p&gt;
&lt;p&gt;Contents of &lt;code&gt;$HOME/.gitconfig&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[includeIf &quot;gitdir:~/work/&quot;]
    path = .gitconfig-work
[includeIf &quot;gitdir:~/personal/&quot;]
    path = .gitconfig-personal&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>Getting rid of DRM on e-books and videos</title>
<link href="https://www.0ink.net/posts/2013/2013-05-22-getting-rid-of-drm-on-e-books-and-videos.html"></link>
<id>urn:uuid:63fb17bb-04ae-a619-9767-3787afe55323</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Instructions on how to remove DRM from E-Books and videos.
How to Remove DRM from Ebooks (and Back Up Your Library Permanently)
The easiest way to strip DRM from Kindle books (and Barnes and Noble, Adobe Digital Content, etc) is with the free ebook software Calibre, DRM removal plugins, and a copy of the Kindle desktop software (PC/Mac). These directions are for Kindle, but will work with Barnes and Noble, Adobe Digital Editions, and older formats. Here's what you need to do:

Download Calibre, the the plugins, and the Kindle Desktop software.
Unzip the contents of the plugin directory.
...]]></summary>
<content type="html">&lt;p&gt;Instructions on how to remove DRM from E-Books and videos.&lt;/p&gt;
&lt;h2 id=&quot;How+to+Remove+DRM+from+Ebooks+%28and+Back+Up+Your+Library+Permanently%29&quot; name=&quot;How+to+Remove+DRM+from+Ebooks+%28and+Back+Up+Your+Library+Permanently%29&quot;&gt;How to Remove DRM from Ebooks (and Back Up Your Library Permanently)&lt;/h2&gt;
&lt;p&gt;The easiest way to strip DRM from Kindle books (and Barnes and Noble, Adobe Digital Content, etc) is with the free ebook software Calibre, DRM removal plugins, and a copy of the Kindle desktop software (PC/Mac). These directions are for Kindle, but will work with Barnes and Noble, Adobe Digital Editions, and older formats. Here&#039;s what you need to do:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Download Calibre, the the plugins, and the Kindle Desktop software.&lt;/li&gt;
&lt;li&gt;Unzip the contents of the plugin directory.&lt;/li&gt;
&lt;li&gt;Open up Calibre and click on &lt;code&gt;&quot;Preferences.&quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Navigate to &lt;code&gt;&quot;Plugins&quot;&lt;/code&gt; under the &lt;code&gt;&quot;Advanced&quot;&lt;/code&gt; section.&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;&quot;Load Plugin from file,&quot;&lt;/code&gt; and select &lt;code&gt;K3MobiDeDRM_v04.5_plugin.zip&lt;/code&gt; from the directory you just unzipped.&lt;/li&gt;
&lt;li&gt;Load up the Kindle app on your Mac or Windows computer and download all your books from Amazon.&lt;/li&gt;
&lt;li&gt;Navigate to either &lt;code&gt;C:Users[your username]DocumentsMy Kindle Content&lt;/code&gt; on Windows or &lt;code&gt;[your username]My DocumentsMy Kindle Content&lt;/code&gt; on Mac.&lt;/li&gt;
&lt;li&gt;Your books aren&#039;t named in any meaningful way, so just drag all the &lt;code&gt;*.azw&lt;/code&gt; files into Calibre.&lt;/li&gt;
&lt;li&gt;After a short wait (depending on the size of your library), Calibre will finish importing the books. Now you have a DRM-free backup of all your books on your computer.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It&#039;s a little convoluted, but once you get the hang of it, Calibre is a solid way to backup all your purchased ebooks.&lt;/p&gt;
&lt;h2 id=&quot;How+to+Remove+DRM+from+Movies+and+TV+Shows&quot; name=&quot;How+to+Remove+DRM+from+Movies+and+TV+Shows&quot;&gt;How to Remove DRM from Movies and TV Shows&lt;/h2&gt;
&lt;p&gt;You can record directly from your computer using a screen recording tool (any of these &lt;a href=&quot;http://lifehacker.com/5839047/five-best-screencasting-or-screen-recording-tools&quot;&gt;five&lt;/a&gt; will do). You will, of course, have to wait for the entire movie since it operates essentially like dubbing, but if you already use screen recording tools it&#039;s a free option for backing up your movies.&lt;/p&gt;</content>
</entry>
<entry>
<title>Wordpress links</title>
<link href="https://www.0ink.net/posts/2013/2013-05-21-wordpress-links.html"></link>
<id>urn:uuid:9fdd6576-1d2e-dac8-71d5-148fbba2d0db</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This article describes how you creeate hyperlinks within
Wordpress.  There are a number of ways to do this, depending
on the configuration and the types of data we are linking to.
Linking Without Using Permalinks
This actually works whether or not Permalinks are active. Using the numeric values found in the ID column of the Posts, Categories, and Pages Administration, you can create links as follows.
Posts
...]]></summary>
<content type="html">&lt;p&gt;This article describes how you creeate hyperlinks within
Wordpress.  There are a number of ways to do this, depending
on the configuration and the types of data we are linking to.&lt;/p&gt;
&lt;h2 id=&quot;Linking+Without+Using+Permalinks&quot; name=&quot;Linking+Without+Using+Permalinks&quot;&gt;Linking Without Using Permalinks&lt;/h2&gt;
&lt;p&gt;This actually works whether or not Permalinks are active. Using the numeric values found in the ID column of the Posts, Categories, and Pages Administration, you can create links as follows.&lt;/p&gt;
&lt;h3 id=&quot;Posts&quot; name=&quot;Posts&quot;&gt;Posts&lt;/h3&gt;
&lt;p&gt;To link to a Post, find the ID of the target post on the Posts administration panel, and insert it in place of the &#039;123&#039; in this link:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;a href=&quot;index.php?p=123&quot;&amp;gt;Post Title&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Categories&quot; name=&quot;Categories&quot;&gt;Categories&lt;/h3&gt;
&lt;p&gt;To link to a Category, find the ID of the target Category on the Categories administration panel, and insert it in place of the &#039;7&#039; in this link:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;a href=&quot;index.php?cat=7&quot;&amp;gt;Category Title&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Pages&quot; name=&quot;Pages&quot;&gt;Pages&lt;/h3&gt;
&lt;p&gt;To link to a Page, find the ID of the target Page on the Pages administration panel, and insert it in place of the &#039;42&#039; in this link:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;a href=&quot;index.php?page_id=42&quot;&amp;gt;Page title&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Date-based+Archives&quot; name=&quot;Date-based+Archives&quot;&gt;Date-based Archives&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Year: &amp;lt;a href=&quot;index.php?m=2006&quot;&amp;gt;2006&amp;lt;/a&amp;gt;
Month: &amp;lt;a href=&quot;index.php?m=200601&quot;&amp;gt;Jan 2006&amp;lt;/a&amp;gt;
Day: &amp;lt;a href=&quot;index.php?m=20060101&quot;&amp;gt;Jan 1, 2006&amp;lt;/a&amp;gt; 
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Linking+Using+Permalinks&quot; name=&quot;Linking+Using+Permalinks&quot;&gt;Linking Using Permalinks&lt;/h2&gt;
&lt;p&gt;If you have enabled permalinks, you have a few additional options for providing links that readers of your site will find a bit more user-friendly than the cryptic numbers. For posts, replace each Structure Tag in your permalink structure with the data appropriate to a post to construct a URL for that post. For example, if the permalink structure is:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/index.php/archives/%year%/%monthnum%/%day%/%postname%/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Replacing the Structure Tags with appropriate values may produce a URL that looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;a href=&quot;/index.php/archives/2005/04/22/my-sample-post/&quot;&amp;gt;My Sample Post&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To obtain an accurate URL for a post it may be easier to navigate to the post within the WordPress blog and then copy the URL from one of the blog links that WordPress generates. Review the information at Using Permalinks for more details on constructing URLs for individual posts.&lt;/p&gt;
&lt;h3 id=&quot;Categories&quot; name=&quot;Categories&quot;&gt;Categories&lt;/h3&gt;
&lt;p&gt;To produce a link to a Category using permalinks, obtain the Category Base value from the Options &amp;gt; Permalinks Administration Panel, and append the category name to the end. For example, to link to the category &amp;quot;testing&amp;quot; when the Category Base is &amp;quot;/index.php/categories&amp;quot;, use the following link:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;a href=&quot;/index.php/categories/testing/&quot;&amp;gt;category link&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can specify a link to a subcategory by using the subcategory directly (as above), or by specifying all parent categories before the category in the URL, like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;a href=&quot;/index.php/categories/parent_category/sub_category/&quot;&amp;gt;subcategory link&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Pages&quot; name=&quot;Pages&quot;&gt;Pages&lt;/h3&gt;
&lt;p&gt;Pages have a hierarchy like Categories, and can have parents. If a Page is at the root level of the hierarchy, you can specify just the Page&#039;s &amp;quot;page slug&amp;quot; after the static part of your permalink structure:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;a href=&quot;/index.php/a-test-page&quot;&amp;gt;a test page&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once again, the best way to verify that this is the correct URL is to navigate to the target Page on the blog and compare the URL to the one you want to use in the link.&lt;/p&gt;
&lt;h3 id=&quot;Date-based+Archives&quot; name=&quot;Date-based+Archives&quot;&gt;Date-based Archives&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Year: &amp;lt;a href=&quot;/index.php/archives/2006&quot;&amp;gt;2006&amp;lt;/a&amp;gt;
Month: &amp;lt;a href=&quot;/index.php/archives/2006/01/&quot;&amp;gt;Jan 2006&amp;lt;/a&amp;gt;
Day: &amp;lt;a href=&quot;/index.php/archives/2006/01/01/&quot;&amp;gt;Jan 1, 2006&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>International Phonetic Alphabet</title>
<link href="https://www.0ink.net/posts/2013/2013-05-21-international-phonetic-alphabet.html"></link>
<id>urn:uuid:77dc18ab-0317-73e7-6205-6609a3af7da4</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Phonetic Alphabet



1
2
...]]></summary>
<content type="html">&lt;p&gt;Phonetic Alphabet&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Alpha&lt;/td&gt;
&lt;td&gt;Kilo&lt;/td&gt;
&lt;td&gt;Uniform&lt;/td&gt;
&lt;td&gt;0 - Zero&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bravo&lt;/td&gt;
&lt;td&gt;Lima&lt;/td&gt;
&lt;td&gt;Victor&lt;/td&gt;
&lt;td&gt;1 - Wun&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Charlie&lt;/td&gt;
&lt;td&gt;Mike&lt;/td&gt;
&lt;td&gt;Whiskey&lt;/td&gt;
&lt;td&gt;2 - Two&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delta&lt;/td&gt;
&lt;td&gt;November&lt;/td&gt;
&lt;td&gt;X-Ray&lt;/td&gt;
&lt;td&gt;3 - Tree&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Echo&lt;/td&gt;
&lt;td&gt;Oscar&lt;/td&gt;
&lt;td&gt;Yankee&lt;/td&gt;
&lt;td&gt;4 - Fower&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Foxtrot&lt;/td&gt;
&lt;td&gt;Papa&lt;/td&gt;
&lt;td&gt;Zulu&lt;/td&gt;
&lt;td&gt;5 - Fife&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Golf&lt;/td&gt;
&lt;td&gt;Quebec&lt;/td&gt;
&lt;td&gt;. decimal&lt;/td&gt;
&lt;td&gt;6 - Six&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hotel&lt;/td&gt;
&lt;td&gt;Romeo&lt;/td&gt;
&lt;td&gt;(point)&lt;/td&gt;
&lt;td&gt;7 -Seven&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;India&lt;/td&gt;
&lt;td&gt;Sierra&lt;/td&gt;
&lt;td&gt;. (full)&lt;/td&gt;
&lt;td&gt;8 - Ait&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Juliet&lt;/td&gt;
&lt;td&gt;Tango&lt;/td&gt;
&lt;td&gt;stop&lt;/td&gt;
&lt;td&gt;9 - Niner&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;</content>
</entry>
<entry>
<title>Makefiles</title>
<link href="https://www.0ink.net/posts/2013/2013-05-20-makefiles.html"></link>
<id>urn:uuid:ab813dd4-bcac-da32-253e-0787855c1037</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Some notes on GNU Make.  I always have to look-up these in
the manual.  Here now for my own convenience.
GNU Make automatic variables:
From http://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html.

$@
...]]></summary>
<content type="html">&lt;p&gt;Some notes on GNU Make.  I always have to look-up these in
the manual.  Here now for my own convenience.&lt;/p&gt;
&lt;h2 id=&quot;GNU+Make+automatic+variables%3A&quot; name=&quot;GNU+Make+automatic+variables%3A&quot;&gt;GNU Make automatic variables:&lt;/h2&gt;
&lt;p&gt;From &lt;a href=&quot;http://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html&quot;&gt;http://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html&lt;/a&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$@&lt;br /&gt;
The file name of the target of the rule.&lt;/li&gt;
&lt;li&gt;$%&lt;br /&gt;
The target member name&lt;/li&gt;
&lt;li&gt;$&amp;lt;&lt;br /&gt;
The name of the first prerequisite.&lt;/li&gt;
&lt;li&gt;$?&lt;br /&gt;
The names of all the prerequisites that are newer.&lt;/li&gt;
&lt;li&gt;$^&lt;br /&gt;
The names of all the prerequisites.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To include files in Makefile only if they exist:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ifneq ($(wildcard _incfile_),)
  include _incfile_
endif
&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>Issue Tracker</title>
<link href="https://www.0ink.net/posts/2013/2013-05-19-issue-tracker.html"></link>
<id>urn:uuid:d8490636-a173-2c29-dc73-38fb2ecc8e64</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[
Use DVCS as backend (GIT)
Output html
markdown
Prefer perl/python
Mostly RO so to avoid merge conflicts.
...]]></summary>
<content type="html">&lt;ul&gt;
&lt;li&gt;Use DVCS as backend (GIT)&lt;/li&gt;
&lt;li&gt;Output html&lt;/li&gt;
&lt;li&gt;markdown&lt;/li&gt;
&lt;li&gt;Prefer perl/python&lt;/li&gt;
&lt;li&gt;Mostly RO so to avoid merge conflicts.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;DITZ+%2B+git+integration&quot; name=&quot;DITZ+%2B+git+integration&quot;&gt;DITZ + git integration&lt;/h3&gt;
&lt;p&gt;Adding Markdown&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;lib/html.rb&lt;/code&gt; contains the functions that generate HTML&lt;/li&gt;
&lt;li&gt;*.rhtml contain templates and call functions in &lt;code&gt;lib/html.rb&lt;/code&gt; to generate (and format) output.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note, if working with &lt;code&gt;github&lt;/code&gt;, refer to &lt;a href=&quot;http://github.github.com/github-flavored-markdown/&quot;&gt;http://github.github.com/github-flavored-markdown/&lt;/a&gt; Using a markdown library:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://maruku.rubyforge.org/usage.html&quot;&gt;http://maruku.rubyforge.org/usage.html&lt;/a&gt;&lt;br /&gt;
Pure Ruby&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://kramdown.rubyforge.org/&quot;&gt;http://kramdown.rubyforge.org/&lt;/a&gt;&lt;br /&gt;
Pure Ruby (fast?)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/rtomayko/rdiscount&quot;&gt;https://github.com/rtomayko/rdiscount&lt;/a&gt;&lt;br /&gt;
C library&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://ruby.morphball.net/bluefeather/index_en.html&quot;&gt;http://ruby.morphball.net/bluefeather/index_en.html&lt;/a&gt;&lt;br /&gt;
Pure Ruby?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Arch has: redcarpet, rdiscount, maruku, ruby-markdown, github-markdown&lt;/p&gt;
&lt;h2 id=&quot;GIT+INTEGRATION&quot; name=&quot;GIT+INTEGRATION&quot;&gt;GIT INTEGRATION&lt;/h2&gt;
&lt;h3 id=&quot;Simple+hooks&quot; name=&quot;Simple+hooks&quot;&gt;Simple hooks&lt;/h3&gt;
&lt;h4 id=&quot;%7E%2F.ditz%2Fhooks%2Fafter_add.rb%3A&quot; name=&quot;%7E%2F.ditz%2Fhooks%2Fafter_add.rb%3A&quot;&gt;~/.ditz/hooks/after_add.rb:&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;Ditz::HookManager.on :after_add do |project, config, issues|
  issues.each do |issue|
    `git add #{issue.pathname}`
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;%7E%2F.ditz%2Fhooks%2Fafter_delete.rb%3A&quot; name=&quot;%7E%2F.ditz%2Fhooks%2Fafter_delete.rb%3A&quot;&gt;~/.ditz/hooks/after_delete.rb:&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;Ditz::HookManager.on :after_delete do |project, config, issues|
  issues.each do |issue|
    `git rm #{issue.pathname}`
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;GIT+Extensions%3A&quot; name=&quot;GIT+Extensions%3A&quot;&gt;GIT Extensions:&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/ihrke/git-ditz&quot;&gt;https://github.com/ihrke/git-ditz&lt;/a&gt; -
Adds a &amp;quot;ditz&amp;quot; subcommand to git. See README on how it installs.&lt;/p&gt;
&lt;h3 id=&quot;DITZ+PLUGINS%3A&quot; name=&quot;DITZ+PLUGINS%3A&quot;&gt;DITZ PLUGINS:&lt;/h3&gt;
&lt;h4 id=&quot;git-sync&quot; name=&quot;git-sync&quot;&gt;git-sync&lt;/h4&gt;
&lt;p&gt;This plugin is useful for when you want synchronized, non-distributed issue&lt;br /&gt;
coordination with other developers, and you&#039;re using git. It allows you to&lt;br /&gt;
synchronize issue updates with other developers by using the &#039;ditz sync`&lt;br /&gt;
command, which does all the git work of sending and receiving issue change&lt;br /&gt;
for you. However, you have to set things up in a very specific way for this&lt;br /&gt;
to work:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Your ditz state must be on a separate branch. I recommend calling it&lt;br /&gt;
&lt;code&gt;bugs&lt;/code&gt;. Create this branch, do a ditz init, and push it to the remote&lt;br /&gt;
repo. (This means you won&#039;t be able to mingle issue change and code&lt;br /&gt;
change in the same commits. If you care.)&lt;/li&gt;
&lt;li&gt;Make a checkout of the bugs branch in a separate directory, but NOT in&lt;br /&gt;
your code checkout. If you&#039;re developing in a directory called &amp;quot;project&amp;quot;,&lt;br /&gt;
I recommend making a ../project-bugs/ directory, cloning the repo there&lt;br /&gt;
as well, and keeping that directory checked out to the &#039;bugs&#039; branch.&lt;br /&gt;
(There are various complicated things you can do to make that directory&lt;br /&gt;
share git objects with your code directory, but I wouldn&#039;t bother unless&lt;br /&gt;
you really care about disk space. Just make it an independent clone.)&lt;/li&gt;
&lt;li&gt;Set that directory as your issue-dir in your .ditz-config file in your&lt;br /&gt;
code checkout directory. (This file should be in .gitignore, btw.)&lt;/li&gt;
&lt;li&gt;Run &#039;ditz reconfigure&#039; and fill in the local branch name, remote&lt;br /&gt;
branch name, and remote repo for the issue tracking branch.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once that&#039;s set up, &#039;ditz sync&#039; will change to the bugs checkout dir, bundle&lt;br /&gt;
up any changes you&#039;ve made to issue status, push them to the remote repo,&lt;br /&gt;
and pull any new changes in too. All ditz commands will read from your bugs&lt;br /&gt;
directory, so you should be able to use ditz without caring about where&lt;br /&gt;
things are anymore. This complicated setup is necessary to avoid accidentally mingling code&lt;br /&gt;
change and issue change. With this setup, issue change is synchronized,&lt;br /&gt;
but how you synchronize code is still up to you. Usage:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;read all the above text very carefully&lt;/li&gt;
&lt;li&gt;add a line &amp;quot;- git-sync&amp;quot; to the .ditz-plugins file in the project&lt;br /&gt;
root&lt;/li&gt;
&lt;li&gt;run &#039;ditz reconfigure&#039; and answer its questions&lt;/li&gt;
&lt;li&gt;run &lt;code&gt;ditz sync&lt;/code&gt; with abandon&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;git+ditz+plugin&quot; name=&quot;git+ditz+plugin&quot;&gt;git ditz plugin&lt;/h4&gt;
&lt;p&gt;This plugin allows issues to be associated with git commits and git&lt;br /&gt;
branches. Git commits can be easily tagged with a ditz issue with the &#039;ditz&lt;br /&gt;
commit&#039; command, and both &#039;ditz show&#039; and the ditz HTML output will then&lt;br /&gt;
contain a list of associated commits for each issue. Issues can also be
assigned a single git feature branch. In this case, all&lt;br /&gt;
commits on that branch will listed as commits for that issue. This&lt;br /&gt;
particular feature is fairly rudimentary, however---|it assumes the reference&lt;br /&gt;
point is the &#039;master&#039; branch, and once the feature branch is merged back&lt;br /&gt;
into master, the list of commits disappears. Two configuration variables are
added, which, when specified, are used to&lt;br /&gt;
construct HTML links for the git commit id and branch names in the generated&lt;br /&gt;
HTML output. Commands added:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ditz set-branch: set the git branch of an issue&lt;/li&gt;
&lt;li&gt;ditz commit: run git-commit, and insert the issue id into the commit&lt;br /&gt;
message.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Usage:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;add a line &amp;quot;- git&amp;quot; to the .ditz-plugins file in the project root&lt;/li&gt;
&lt;li&gt;run ditz reconfigure, and enter the URL prefixes, if any, from&lt;br /&gt;
which to create commit and branch links.&lt;/li&gt;
&lt;li&gt;use &#039;ditz commit&#039; with abandon.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;COLLABORATION+PLUGINS&quot; name=&quot;COLLABORATION+PLUGINS&quot;&gt;COLLABORATION PLUGINS&lt;/h4&gt;
&lt;h5 id=&quot;issue-claiming&quot; name=&quot;issue-claiming&quot;&gt;issue-claiming&lt;/h5&gt;
&lt;p&gt;This plugin allows people to claim issues. This is useful for avoiding&lt;br /&gt;
duplication of work---|you can check to see if someone&#039;s claimed an&lt;br /&gt;
issue before starting to work on it, and you can let people know what&lt;br /&gt;
you&#039;re working on. Commands added:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ditz claim: claim an issue for yourself or a dev specified in project.yaml&lt;/li&gt;
&lt;li&gt;ditz unclaim: unclaim a claimed issue&lt;/li&gt;
&lt;li&gt;ditz mine: show all issues claimed by you&lt;/li&gt;
&lt;li&gt;ditz claimed: show all claimed issues, by developer&lt;/li&gt;
&lt;li&gt;ditz unclaimed: show all unclaimed issues&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Usage:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;add a line &amp;quot;- issue-claiming&amp;quot; to the .ditz-plugins file in the project&lt;br /&gt;
root&lt;/li&gt;
&lt;li&gt;(optional:) add a &#039;devs&#039; key to project.yaml, e.g:&lt;/li&gt;
&lt;/ol&gt;
&lt;h5 id=&quot;issue+labeling&quot; name=&quot;issue+labeling&quot;&gt;issue labeling&lt;/h5&gt;
&lt;p&gt;This plugin allows label issues. This can replace the issue component&lt;br /&gt;
and/or issue types (bug,feature,task), by providing a more flexible&lt;br /&gt;
to organize your issues. Commands added:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ditz new_label [label]: create a new label for the project&lt;/li&gt;
&lt;li&gt;ditz label : label an issue with some labels&lt;/li&gt;
&lt;li&gt;ditz unlabel [labels]: remove some label(s) of an issue&lt;/li&gt;
&lt;li&gt;ditz labeled [release]: show all issues with these labels&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Usage:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;add a line &amp;quot;- issue-labeling&amp;quot; to the .ditz-plugins file in the project&lt;br /&gt;
root&lt;/li&gt;
&lt;li&gt;use the above commands to abandon&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;TODO:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;extend the HTML view to have per-labels listings&lt;/li&gt;
&lt;li&gt;allow for more compact way to type them (completion, prefixes...)&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;issue+priority&quot; name=&quot;issue+priority&quot;&gt;issue priority&lt;/h5&gt;
&lt;p&gt;This plugin allows issues to have priorities. Priorities are numbers&lt;br /&gt;
P1-P5 where P1 is the highest priority and P5 is the lowest. Internally&lt;br /&gt;
the priorities are sorted lexicographically. Commands added:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ditz set-priority : Set the priority of an issue&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Usage:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;add a line &amp;quot;- issue-priority&amp;quot; to the .ditz-plugins file in the project&lt;br /&gt;
root&lt;/li&gt;
&lt;li&gt;use the above commands to abandon&lt;/li&gt;
&lt;/ol&gt;</content>
</entry>
<entry>
<title>Cleaning up Google Calendar</title>
<link href="https://www.0ink.net/posts/2013/2013-05-19-google-services-tips.html"></link>
<id>urn:uuid:e9fc8815-7d4a-6fbe-e9ac-40baabc95536</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Recipe for cleaning a google calendar.

Sign in to Google Calendar
Click on Calendar Settings (current version has this just above the list of personal calendars, under an arrow).
Click on &quot;Delete&quot; of the main calendar.
A confirmation dialog box appears telling you that that &quot;This deletes all events on primary Calendar&quot;.
...]]></summary>
<content type="html">&lt;p&gt;Recipe for cleaning a google calendar.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Sign in to Google Calendar&lt;/li&gt;
&lt;li&gt;Click on Calendar Settings (current version has this just above the list of personal calendars, under an arrow).&lt;/li&gt;
&lt;li&gt;Click on &amp;quot;Delete&amp;quot; of the main calendar.&lt;/li&gt;
&lt;li&gt;A confirmation dialog box appears telling you that that &amp;quot;This deletes all events on primary Calendar&amp;quot;.&lt;/li&gt;
&lt;/ol&gt;</content>
</entry>
<entry>
<title>Automatically adding systems to an AD domain</title>
<link href="https://www.0ink.net/posts/2013/2013-05-19-automatically-adding-systems-to-an-ad-domain.html"></link>
<id>urn:uuid:eb2b1211-f195-a202-3479-387b1fcf4308</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[When using virtualisation it is very common to create template VMs
that can be cloned from. This makes deployment much easier than having
to install a new VM from scratch. Unfortunately, the cloned VMs lack
any Active Directory memberships and the VMs have to be manually
added to the AD domain. For automated deployment scenarios this is
less than desirable. This recipe intends to solve that issue in a
...]]></summary>
<content type="html">&lt;p&gt;When using virtualisation it is very common to create &lt;em&gt;template&lt;/em&gt; VMs
that can be cloned from. This makes deployment much easier than having
to install a new VM from scratch. Unfortunately, the cloned VMs lack
any Active Directory memberships and the VMs have to be &lt;em&gt;manually&lt;/em&gt;
added to the AD domain. For automated deployment scenarios this is
less than desirable. This recipe intends to solve that issue in a
&lt;em&gt;Hypervisor&lt;/em&gt; independant manner. This recipe uses a Visual Basic
script that will automatically join a system to a domain during
Windows system preparation. In Lab Manager these steps can be
performed on a VM Template so that virtual machines cloned from it
will be joined to the domain when the system customization process
runs. A specific Active Directory Organizational Unit can be
specified. The Visual Basic script will contain credentials used for
joining the system to the domain. So, as a security measure the Visual
Basic script is setup to be deleted at the end of a successful
execution.&lt;/p&gt;
&lt;h3 id=&quot;Prerequisites&quot; name=&quot;Prerequisites&quot;&gt;Prerequisites&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Active Directory User Account with permissions to add Computer Objects.&lt;/li&gt;
&lt;li&gt;LDAP path syntax to Active Directory Organizational Unit to add the Computer to.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Steps+on+the+VM+Template&quot; name=&quot;Steps+on+the+VM+Template&quot;&gt;Steps on the VM Template&lt;/h3&gt;
&lt;h4 id=&quot;Create+Scripts+Folder&quot; name=&quot;Create+Scripts+Folder&quot;&gt;Create Scripts Folder&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;C:\Windows\Setup\Scripts&lt;/code&gt;&lt;/p&gt;
&lt;h4 id=&quot;Create+Batch+File&quot; name=&quot;Create+Batch+File&quot;&gt;Create Batch File&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;C:\Windows\Setup\SetupComplete.cmd&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Start /wait cscript %WINDIR%\Setup\Scripts\AddDomain.vbs
Del %WINDIR%\Setup\Scripts\AddDomain.vbs
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;Create+VBS+File&quot; name=&quot;Create+VBS+File&quot;&gt;Create VBS File&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;C:\Windows\Setup\Scripts\AddDomain.vbs&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-vbs&quot;&gt;Const JOIN_DOMAIN             = 1
Const ACCT_CREATE             = 2
Const ACCT_DELETE             = 4
Const WIN9X_UPGRADE           = 16
Const DOMAIN_JOIN_IF_JOINED   = 32
Const JOIN_UNSECURE           = 64
Const MACHINE_PASSWORD_PASSED = 128
Const DEFERRED_SPN_SET        = 256
Const INSTALL_INVOCATION      = 262144

strDomain   = &quot;DomainName&quot;
strOU       = &quot;LDAP\OU\PATH&quot;
strUser     = &quot;Domain\Username&quot;
strPassword = &quot;Password&quot;

Set objNetwork = CreateObject(&quot;WScript.Network&quot;)
strComputer = objNetwork.ComputerName

Set objComputer = _
  GetObject(&quot;winmgmts:{impersonationLevel=Impersonate}!&quot; &amp;amp; _
  strComputer &amp;amp; &quot;rootcimv2:Win32_ComputerSystem.Name=&#039;&quot; _
  &amp;amp; strComputer &amp;amp; &quot;&#039;&quot;)

ReturnValue = objComputer.JoinDomainOrWorkGroup(strDomain, _
   strPassword, _
   strDomain &amp;amp; &quot;\&quot; &amp;amp; strUser, _
   strOU, _
   JOIN_DOMAIN + ACCT_CREATE)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tip: Start Notepad as administrator to have save access to the folder.
Set the correct values for &lt;code&gt;StrDomain&lt;/code&gt;, &lt;code&gt;StrOU&lt;/code&gt;, &lt;code&gt;StrUser&lt;/code&gt; and &lt;code&gt;StrPassword&lt;/code&gt;
Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    strDomain = &quot;best.adinternal.com&quot; 
    strOU = &quot;ou=Virtuals,ou=CRE R&amp;amp;D,ou=Beaverton,ou=Shared Management,dc=best,dc=adinternal,dc=com&quot; 
    strUser&amp;amp; = &quot;_adjoinuser&quot; 
    strPassword = &quot;$uperS3curePassw()rd!{13245}&quot; 
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Deploy+VM&quot; name=&quot;Deploy+VM&quot;&gt;Deploy VM&lt;/h3&gt;
&lt;p&gt;Be sure &lt;code&gt;Perform customization&lt;/code&gt; is checked and &lt;code&gt;Microsoft Sysprep&lt;/code&gt; is
selected on the VM Template properties. &lt;code&gt;Clone the VM Template&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Tip: Wait around 10 minutes before trying to login to the VM. During
this time the VM is going through the sysprep process which will change
the hostname to the name specified when cloning the VM to a
configuration and join the domain. The process should be complete when
the login screen displays &lt;strong&gt;[Ctrl]+[Alt]+[Delete]&lt;/strong&gt; and prompts
for a domain login.&lt;/p&gt;
&lt;h3 id=&quot;Additional+notes&quot; name=&quot;Additional+notes&quot;&gt;Additional notes&lt;/h3&gt;
&lt;p&gt;To improve security we could for example not hardcode login credentials
in the VB script. Instead, we could retrieve them from a web server
(using SSL). This server could reset the Login password for the
addDomain account and send that. Once this is completed, the password
could be reset again. Also, the web server could check the IP address
and referencing DNS/DHCP to see if this machine is indeed being
authorised. Finally, we can place this in a different AD domain (with
the appropriate trust relationships) so that you can apply additional
security policies.&lt;/p&gt;
&lt;h3 id=&quot;References&quot; name=&quot;References&quot;&gt;References&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;This recipe was originally published at: &lt;a href=&quot;http://www.bonusbits.com/main/HowTo:Setup_a_VM_to_Automatically_Join_to_a_Domain&quot;&gt;http://www.bonusbits.com/main/HowTo:Setup_a_VM_to_Automatically_Join_to_a_Domain&lt;/a&gt;&lt;br /&gt;
Unfortunately I am no longer able to reach this site.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&amp;amp;cmd=displayKC&amp;amp;externalId=1007491&quot;&gt;http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&amp;amp;cmd=displayKC&amp;amp;externalId=1007491&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://msdn.microsoft.com/en-us/library/windows/desktop/aa392154%28v=vs.85%29.aspx&quot;&gt;http://msdn.microsoft.com/en-us/library/windows/desktop/aa392154%28v=vs.85%29.aspx&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Other examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://mail-archives.apache.org/mod_mbox/incubator-vcl-user/201112.mbox/%3CCAD7o_XxD2a9j0+V7faE1nTSt-OMT+W9=y4TCvKZ-3q92+czewQ@mail.gmail.com%3E&quot;&gt;http://mail-archives.apache.org/mod_mbox/incubator-vcl-user/201112.mbox/%3CCAD7o_XxD2a9j0+V7faE1nTSt-OMT+W9=y4TCvKZ-3q92+czewQ@mail.gmail.com%3E&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.virtualizationteam.com/virtualization-vmware/vcloud-director/vcloud-director-joining-vms-to-specific-active-directory-domain-ou.html&quot;&gt;http://www.virtualizationteam.com/virtualization-vmware/vcloud-director/vcloud-director-joining-vms-to-specific-active-directory-domain-ou.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://itnervecenter.com/content/deploying-window-server-2008-r2-vmware-template-and-joining-it-domain&quot;&gt;http://itnervecenter.com/content/deploying-window-server-2008-r2-vmware-template-and-joining-it-domain&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://blogs.citrix.com/2011/09/16/xenclient-auto-join-vms-to-the-activedirectory/&quot;&gt;http://blogs.citrix.com/2011/09/16/xenclient-auto-join-vms-to-the-activedirectory/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>SATA/IDE warm plug/unplug</title>
<link href="https://www.0ink.net/posts/2013/2013-05-18-sataide-warm-plugunplug.html"></link>
<id>urn:uuid:71b2ee04-a899-266e-0f08-d44946243471</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This is for SATA and IDE interfaces that do not automatically detect added/removed devices.
Scanning for newly added discs:
echo "- - -" &gt; /sys/class/scsi_host/host0/scan

safely removing a disk
echo 1 &gt; /sys/block/sda/device/delete
...]]></summary>
<content type="html">&lt;p&gt;This is for SATA and IDE interfaces that do not automatically detect added/removed devices.&lt;/p&gt;
&lt;h3 id=&quot;Scanning+for+newly+added+discs%3A&quot; name=&quot;Scanning+for+newly+added+discs%3A&quot;&gt;Scanning for newly added discs:&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;echo &quot;- - -&quot; &amp;gt; /sys/class/scsi_host/host0/scan
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;safely+removing+a+disk&quot; name=&quot;safely+removing+a+disk&quot;&gt;safely removing a disk&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;echo 1 &amp;gt; /sys/block/sda/device/delete
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Other+notes...&quot; name=&quot;Other+notes...&quot;&gt;Other notes...&lt;/h3&gt;
&lt;p&gt;In the &lt;em&gt;HP MicroServer&lt;/em&gt;, we can identify the host to scan by:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;head -1 /sys/class/scsi_host/host*/proc_name
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And look for the &lt;code&gt;pata_atiixp&lt;/code&gt;. Should show &lt;code&gt;ahci&lt;/code&gt; and &lt;code&gt;usb-storage&lt;/code&gt; too.&lt;/p&gt;</content>
</entry>
<entry>
<title>Icons</title>
<link href="https://www.0ink.net/posts/2013/2013-05-17-icons.html"></link>
<id>urn:uuid:5cfe4dbc-0acc-1959-e2d0-f16b58afcc56</id>
<updated>2023-04-25T00:00:00+02:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Finding icons:
iconfinder.
In 2023 I am using:
icons8
This allows to download icons in different formats, and also allows
you to do tweaks such as:
...]]></summary>
<content type="html">&lt;p&gt;Finding icons:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.iconfinder.com/&quot;&gt;iconfinder&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In 2023 I am using:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://icons8.com/&quot;&gt;icons8&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This allows to download icons in different formats, and also allows
you to do tweaks such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;changing colors&lt;/li&gt;
&lt;li&gt;adding sub-icons&lt;/li&gt;
&lt;li&gt;resizing&lt;/li&gt;
&lt;li&gt;etc...&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>On-line Web Authoring Resources</title>
<link href="https://www.0ink.net/posts/2013/2013-05-16-web-authoring-tips.html"></link>
<id>urn:uuid:4601fe64-1921-cc15-4328-f7f7280bccd4</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[A collection of links for Web-Authoring.  This focuses on using
Web (HTML and CSS) technologies directly and not through a
CMS like Wordpress.

http://www.webestools.com/
Free online tools, generators, services, scripts, tutorials.
...]]></summary>
<content type="html">&lt;p&gt;A collection of links for Web-Authoring.  This focuses on using
Web (HTML and CSS) technologies directly and not through a
CMS like Wordpress.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.webestools.com/&quot;&gt;http://www.webestools.com/&lt;/a&gt;&lt;br /&gt;
Free online tools, generators, services, scripts, tutorials.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.google.com/webmasters/tools&quot;&gt;http://www.google.com/webmasters/tools&lt;/a&gt;&lt;br /&gt;
Google Webmaster tools, gives you a peek of how your website&lt;br /&gt;
looks from google search engines perspective.&lt;/li&gt;
&lt;li&gt;Free templates&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.freecsstemplates.org/&quot;&gt;http://www.freecsstemplates.org/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://csscreme.com/&quot;&gt;http://csscreme.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.templatemo.com/&quot;&gt;http://www.templatemo.com/&lt;/a&gt; (Unrestricted)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.free-css.com/&quot;&gt;http://www.free-css.com/&lt;/a&gt; (GPL or CC)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.free-css-templates.com/&quot;&gt;http://www.free-css-templates.com/&lt;/a&gt; (CC)&lt;/li&gt;
&lt;/ul&gt;</content>
</entry>
<entry>
<title>First steps...</title>
<link href="https://www.0ink.net/posts/2013/2013-05-16-first-steps.html"></link>
<id>urn:uuid:45f493a0-345d-4ded-457f-ecb4202cc417</id>
<updated>2024-10-22T00:00:00+02:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[So finally took the time to re-launch the 0ink web site. This time used more off-the shelf
software. So this site is just a another plain wordpress powered site.
Actually I have to thank my son for introducing me to wordpress.
What happened is that my son, who is only seven wanted to have his own web site. (Due to peer
pressure, kids these days...)
He has an Android tablet that he uses quite often. Since I knew that wordpress can be
...]]></summary>
<content type="html">&lt;p&gt;So finally took the time to re-launch the &lt;code&gt;0ink&lt;/code&gt; web site. This time used more off-the shelf
software. So this site is just a another plain &lt;a href=&quot;http://wordpress.org&quot;&gt;wordpress powered&lt;/a&gt; site.
Actually I have to thank my son for &lt;em&gt;introducing&lt;/em&gt; me to &lt;strong&gt;wordpress.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;What happened is that my son, who is only seven wanted to have his own web site. (Due to peer
pressure, kids these days...)&lt;/p&gt;
&lt;p&gt;He has an Android tablet that he uses quite often. Since I knew that &lt;strong&gt;wordpress&lt;/strong&gt; can be
used to make decent looking web sites and there even was an Android app. Also knew that
free &lt;strong&gt;wordpress&lt;/strong&gt; hosting sites can easily be found... Make a story short, I set him up with
a &lt;a href=&quot;http://wordpress.com/&quot;&gt;http://wordpress.com/&lt;/a&gt; account and he was live on the &#039;Net in a matter
of minutes. His website can be found &lt;a href=&quot;http://sebitoliu.wordpress.com/&quot;&gt;here&lt;/a&gt;. This first foray got
me intrigued, so I tested it on another free hosting &lt;a href=&quot;http://s12.pw/&quot;&gt;site (here)&lt;/a&gt; and found it
quite powerful so decided to use it for &lt;code&gt;0ink.net&lt;/code&gt; which seriously needed to move to a new host.&lt;/p&gt;
&lt;p&gt;The old hosting service &lt;a href=&quot;http://www.110mb.com/&quot;&gt;110mb&lt;/a&gt; had been taken over by a
&lt;strong&gt;new management team&lt;/strong&gt; and the new free hosting service was not as appealing as before.
Add in a little bit of bit-rot and that site quickly became an ugly mess. So now we are
back again, and hopefully will be more maintainable.&lt;/p&gt;</content>
</entry>
<entry>
<title>We are back...</title>
<link href="https://www.0ink.net/posts/2013/2013-05-15-we-are-back.html"></link>
<id>urn:uuid:9f05bff4-7311-e1b5-0a64-091df5560c73</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[After a long time in Limbo we are back with 0ink.net as a live
site.
...]]></summary>
<content type="html">&lt;p&gt;After a long time in &lt;strong&gt;Limbo&lt;/strong&gt; we are back with &lt;code&gt;0ink.net&lt;/code&gt; as a live
site.&lt;/p&gt;</content>
</entry>
<entry>
<title>Local Perl packages</title>
<link href="https://www.0ink.net/posts/2013/2013-05-15-perl-tips.html"></link>
<id>urn:uuid:34f9f5be-68c0-071e-05c2-82f21a16f5ea</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Determine what is the local PERL5LIB configuration:
LIB=$(
  for d in `tr : ' ' &lt;&lt;&lt;$PERL5LIB
  do
    if [ -w $d ] ; then
      echo $d
...]]></summary>
<content type="html">&lt;p&gt;Determine what is the local PERL5LIB configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;LIB=$(
  for d in `tr : &#039; &#039; &amp;lt;&amp;lt;&amp;lt;$PERL5LIB
  do
    if [ -w $d ] ; then
      echo $d
    break
  fi
  done)
PREFIX=`dirname $LIB`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Install sequence&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#PREFIX=$HOME/cpan
#LIB=$HOME/cpan/lib
tar zxvf $perl-mod-tar
cd $unpacked-src-dir
perl Makefile.PL PREFIX=$PREFIX LIB=$LIB &quot;$@&quot;
make
make test
make install
&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>Linux Keyboard Tips</title>
<link href="https://www.0ink.net/posts/2013/2013-05-13-linux-keyboard-tips.html"></link>
<id>urn:uuid:1e9143b2-480d-e7d7-ad1c-e0ea552275ac</id>
<updated>2022-03-03T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[Miscellaneous hacks to use the keyboard under Linux.
Special Characters on X11
The compose key, when pressed in sequence with other keys, produces a
Unicode character. E.g., in most configurations pressing Compose e
``` produces &eacute;. Compose keys appeared on some computer keyboards
decades ago, especially those produced by Sun Microsystems. However,
...]]></summary>
<content type="html">&lt;p&gt;Miscellaneous hacks to use the keyboard under Linux.&lt;/p&gt;
&lt;h3 id=&quot;Special+Characters+on+X11&quot; name=&quot;Special+Characters+on+X11&quot;&gt;Special Characters on X11&lt;/h3&gt;
&lt;p&gt;The compose key, when pressed in sequence with other keys, produces a
Unicode character. E.g., in most configurations pressing &lt;code&gt;Compose&lt;/code&gt; &lt;code&gt;e&lt;/code&gt;
``` produces &amp;eacute;. Compose keys appeared on some computer keyboards
decades ago, especially those produced by Sun Microsystems. However,
it can be enabled on any keyboard with setxkbmap. For example, compose
can be set to right alt by running:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;setxkbmap -option compose:ralt&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Compose+Sequences&quot; name=&quot;Compose+Sequences&quot;&gt;Compose Sequences&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;    |   no-break space                      &amp;amp;brvbar;  broken bar              ||
    &amp;amp;shy;  soft hyphen             --           &amp;amp;micro;  micro sign              /U
    &amp;amp;iexcl;  inverted !              !!           &amp;amp;iquest;  inverted ?              ??
    &amp;amp;cent;  cent sign            C/ or C|        &amp;amp;pound;  pound sign           L- or L=
    &amp;amp;curren;  currency sign        XO or X0        &amp;amp;yen;  yen sign             Y- or Y=
    &amp;amp;sect;  section sign         SO or S! or S0  &amp;amp;para;  pilcrow sign            P!
    &amp;amp;uml;  diaeresis            &amp;amp;quot;&amp;amp;quot; or  &amp;amp;quot;        &amp;amp;macr;  macron               _^ or -^
    &amp;amp;acute;  acute accent            &#039;&#039;           &amp;amp;cedil;  cedilla                 ,,
    &amp;amp;copy;  copyright sign       CO or C0        &amp;amp;reg;  registered sign         RO
    &amp;amp;ordf;  feminine ordinal        A_           &amp;amp;ordm;  masculine ordinal       O_
    &amp;amp;laquo;  opening angle brackets  &amp;amp;amp;lt;&amp;amp;amp;lt;           &amp;amp;raquo;  closing angle brakets   &amp;amp;amp;gt;&amp;amp;amp;gt;
    &amp;amp;deg;  degree sign             0^           &amp;amp;sup1;  superscript 1           1^
    &amp;amp;sup2;  superscript 2           2^           &amp;amp;sup3;  superscript 3           3^
    &amp;amp;plusmn;  plus or minus sign      +-           &amp;amp;frac14;  fraction one-quarter    14
    &amp;amp;frac12;  fraction one-half       12           &amp;amp;frac34;  fraction three-quarter  34
    &amp;amp;middot;  middle dot           .^ or ..        &amp;amp;not;  not sign                -,
    &amp;amp;times;  multiplication sign     xx           &amp;amp;divide;  division sign           :-

    &amp;amp;Agrave;  A grave                 A`           &amp;amp;agrave;  a grave                 a`
    &amp;amp;Aacute;  A acute                 A&#039;           &amp;amp;aacute;  a acute                 a&#039;
       A circumflex            A^           &amp;amp;acirc;  a circumflex            a^
    &amp;amp;Atilde;  A tilde                 A~           &amp;amp;atilde;  a tilde                 a~
    &amp;amp;Auml;  A diaeresis             A&amp;amp;quot;           &amp;amp;auml;  a diaeresis             a&amp;amp;quot;
    &amp;amp;Aring;  A ring                  A*           &amp;amp;aring;  a ring                  a*
    &amp;amp;AElig;  AE ligature             AE           &amp;amp;aelig;  ae ligature             ae

    &amp;amp;Ccedil;  C cedilla               C,           &amp;amp;ccedil;  c cedilla               c,

    &amp;amp;Egrave;  E grave                 E`           &amp;amp;amp;egrave;  e grave                 e`
    &amp;amp;Eacute;  E acute                 E&#039;           &amp;amp;amp;eacute;  e acute                 e&#039;
    &amp;amp;Ecirc;  E circumflex            E^           &amp;amp;ecirc;  e circumflex            e^
    &amp;amp;Euml;  E diaeresis             E&amp;amp;quot;           &amp;amp;amp;euml;  e diaeresis             e&amp;amp;quot;

    &amp;amp;Igrave;  I grave                 I`           &amp;amp;igrave;  i grave                 i`
    &amp;amp;Iacute;  I acute                 I&#039;           &amp;amp;iacute;  i acute                 i&#039;
    &amp;amp;Icirc;  I circumflex            I^           &amp;amp;icirc;  i circumflex            i^
    &amp;amp;Iuml;  I diaeresis             I&amp;amp;quot;           &amp;amp;iuml;  i diaeresis             i&amp;amp;quot;

    &amp;amp;ETH;  capital eth             D-           &amp;amp;eth;  small eth               d-

    &amp;amp;Ntilde;  N tilde                 N~           &amp;amp;ntilde;  n tilde                 n~

    &amp;amp;Ograve;  O grave                 O`           &amp;amp;ograve;  o grave                 o`
    &amp;amp;Oacute;  O acute                 O&#039;           &amp;amp;oacute;  o acute                 o&#039;
    &amp;amp;Ocirc;  O circumflex            O^           &amp;amp;ocirc;  o circumflex            o^
    &amp;amp;Otilde;  O tilde                 O~           &amp;amp;otilde;  o tilde                 o~
    &amp;amp;Ouml;  O diaeresis             O&amp;amp;quot;           &amp;amp;ouml;  o diaeresis             o&amp;amp;quot;
    &amp;amp;Oslash;  O slash                 O/           &amp;amp;oslash;  o slash                 o/

    &amp;amp;Ugrave;  U grave                 U`           &amp;amp;ugrave;  u grave                 u`
    &amp;amp;Uacute;  U acute                 U&#039;           &amp;amp;uacute;  u acute                 u&#039;
    &amp;amp;Ucirc;  U circumflex            U^           &amp;amp;ucirc;  u circumflex            u^
    &amp;amp;Uuml;  U diaeresis             U&amp;amp;quot;           &amp;amp;uuml;  u diaeresis             u&amp;amp;quot;

    &amp;amp;Yacute;  Y acute                 Y&#039;           &amp;amp;yacute;  y acute                 y&#039;

    &amp;amp;THORN;  capital thorn           TH           &amp;amp;thorn;  small thorn             th

    &amp;amp;szlig;  German small sharp s    ss           &amp;amp;yuml;  y diaeresis             y&amp;amp;quot;

      Euro                    e=&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Environment+variables&quot; name=&quot;Environment+variables&quot;&gt;Environment variables&lt;/h3&gt;
&lt;p&gt;Some unfriendly applications (including many GTK apps) will override
the compose key and default to their own built-in combinations. You
can typically fix this by setting environment variables; for instance,
you can fix the behavior for GTK with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export GTK_IM_MODULE=xim&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
<entry>
<title>Bash Tips</title>
<link href="https://www.0ink.net/posts/2013/2013-05-13-bash-tips.html"></link>
<id>urn:uuid:7c90e003-49d7-efc5-b355-064546463b89</id>
<updated>2023-12-09T00:00:00+01:00</updated>
<author><name>alex</name>
</author>
<summary type="html"><![CDATA[Some bash one-liners:
echo ${!X*}

Will print all the names of variables whos name starts with X. To
output the contents of a variable so it can be parsed by
bash
...]]></summary>
<content type="html">&lt;p&gt;Some bash one-liners:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;echo ${!X*}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Will print all the names of variables whos name starts with &lt;code&gt;X&lt;/code&gt;. To
output the contents of a variable so it can be parsed by&lt;br /&gt;
bash&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;declare -p VARNAME
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;Pattern+Matching&quot; name=&quot;Pattern+Matching&quot;&gt;Pattern Matching&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;Operator: ${foo#t*is}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Function: deletes the shortest possible match from the left&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Operator: ${foo##t*is}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Function: deletes the longest possible match from the left&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Operator: ${foo%t*st}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Function: deletes the shortest possible match from the right&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Operator: ${foo%%t*st}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Function: deletes the longest possible match from the right MNEMONIC:
The # key is on the left side of the $ key and operates from the left.
The % key is on the right of the $ key and operates from the right.&lt;/p&gt;
&lt;h2 id=&quot;Substitution&quot; name=&quot;Substitution&quot;&gt;Substitution&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;Operator: ${foo:-bar}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Function: If $foo exists and is not null, return $foo. If it doesn&#039;t
exist or is null, return bar.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Operator: ${foo:=bar}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Function: If $foo exists and is not null, return $foo. If it doesn&#039;t
exist or is null, set $foo to bar and return bar.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Operator: ${foo:+bar}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Function: If $foo exists and is not null, return bar. If it doesn&#039;t
exist or is null, return a null.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Operator: ${foo:?&quot;error message&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Function: If $foo exists and isn&#039;t null, return its value. If it
doesn&#039;t exist or is null, print the error message. If no error message
is given, it prints parameter null or not set. In a non-interactive
shell, this aborts the current script. In an interactive shell, this
simply prints the error message.&lt;/p&gt;
&lt;h2 id=&quot;%24%24+for+Subshell&quot; name=&quot;%24%24+for+Subshell&quot;&gt;$$ for Subshell&lt;/h2&gt;
&lt;p&gt;When running a sub-shell in &lt;code&gt;bash&lt;/code&gt; the &lt;code&gt;$$&lt;/code&gt; construct still returns
the process id of the main shell. Use the following construct to
determine the correct IP address:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mypid=$(sh -c &#039;echo $$PPID&#039;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yes, it looks &lt;em&gt;nasty&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;Retrieving+an+IP+address.&quot; name=&quot;Retrieving+an+IP+address.&quot;&gt;Retrieving an IP address.&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Updated 2023-10-20&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;To get the IP address, the easiest way is to use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ip -br a&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Results in:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;lo               UNKNOWN        127.0.0.1/8 ::1/128 
enp1s0           DOWN           
eno1             UP             192.168.101.64/24 fd42:bf4e:715f:6ef5:95b0:ecc9:68fa:ac07/64 fe80::82db:4389:522c:332d/64 
wlp3s0           DOWN           
virbr0           DOWN           192.168.122.1/24 
docker0          UP             172.17.0.1/16 fe80::42:95ff:fe8e:29b0/64 
br-6f79780fe7d1  DOWN           172.18.0.1/16 
veth8758d36@if8  UP             fe80::a84f:c3ff:fe8f:45b/64 &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which is easy to parse.  Another option is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ip -o a&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Results:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1: lo    inet 127.0.0.1/8 scope host lo\       valid_lft forever preferred_lft forever
1: lo    inet6 ::1/128 scope host proto kernel_lo \       valid_lft forever preferred_lft forever
3: eno1    inet 192.168.101.64/24 brd 192.168.101.255 scope global dynamic noprefixroute eno1\       valid_lft 26700sec preferred_lft 26700sec
3: eno1    inet6 fd42:bf4e:715f:6ef5:95b0:ecc9:68fa:ac07/64 scope global dynamic noprefixroute \       valid_lft 1534sec preferred_lft 1534sec
3: eno1    inet6 fe80::82db:4389:522c:332d/64 scope link noprefixroute \       valid_lft forever preferred_lft forever
5: virbr0    inet 192.168.122.1/24 brd 192.168.122.255 scope global virbr0\       valid_lft forever preferred_lft forever
6: docker0    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0\       valid_lft forever preferred_lft forever
6: docker0    inet6 fe80::42:95ff:fe8e:29b0/64 scope link proto kernel_ll \       valid_lft forever preferred_lft forever
7: br-6f79780fe7d1    inet 172.18.0.1/16 brd 172.18.255.255 scope global br-6f79780fe7d1\       valid_lft forever preferred_lft forever
9: veth8758d36    inet6 fe80::a84f:c3ff:fe8f:45b/64 scope link proto kernel_ll \       valid_lft forever preferred_lft forever&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Has more information, but it is still fairly parsable.&lt;/p&gt;
&lt;h2 id=&quot;Identifying+virtual+Network+Interfaces&quot; name=&quot;Identifying+virtual+Network+Interfaces&quot;&gt;Identifying virtual Network Interfaces&lt;/h2&gt;
&lt;p&gt;If you need to identify which network interfaces are virtua, use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;readlink /sys/class/net/virbr0&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Results in:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;../../devices/virtual/net/virbr0&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output contains &lt;code&gt;/virtual/&lt;/code&gt; in the path.  Physical NICs would have something related to the bus
that the NIC is connected to.&lt;/p&gt;</content>
</entry>
<entry>
<title>Keep e-mail private</title>
<link href="https://www.0ink.net/posts/2013/2013-05-09-keep-e-mail-private.html"></link>
<id>urn:uuid:2674d311-8d1c-1a37-38cd-8e7f02eff0b6</id>
<updated>2021-12-25T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[This is a handy tip.
If you don't want to give out your real email address to register for
a site and don't want to go through the hassle of creating a spam
email address, just point your browser to
Guerrilla Mail
upon loading the page you'll have an automatically assigned email
...]]></summary>
<content type="html">&lt;p&gt;This is a handy tip.&lt;/p&gt;
&lt;p&gt;If you don&#039;t want to give out your real email address to register for
a site and don&#039;t want to go through the hassle of creating a spam
email address, just point your browser to
&lt;a href=&quot;https://www.guerrillamail.com/&quot;&gt;Guerrilla Mail&lt;/a&gt;
upon loading the page you&#039;ll have an automatically assigned email
address that is good for one hour.&lt;/p&gt;
&lt;p&gt;Hopefully this helps reduce spam.&lt;/p&gt;</content>
</entry>
<entry>
<title>Git Workflows</title>
<link href="https://www.0ink.net/posts/2013/2013-05-09-git-workflows.html"></link>
<id>urn:uuid:f620760c-2408-73a7-a977-1c926bc2cf05</id>
<updated>2024-11-22T00:00:00+01:00</updated>
<author><name>Alejandro Liu</name>
</author>
<summary type="html"><![CDATA[
Start working on a Topic Branch
Keep Topic Branch current
Merge a Topic Branch
Start working on a HotFix
Keep HotFix Branch current
...]]></summary>
<content type="html">&lt;div id=&quot;toc&quot;&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#Start+working+on+a+Topic+Branch&quot;&gt;Start working on a Topic Branch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Keep+Topic+Branch+current&quot;&gt;Keep Topic Branch current&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Merge+a+Topic+Branch&quot;&gt;Merge a Topic Branch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Start+working+on+a+HotFix&quot;&gt;Start working on a HotFix&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Keep+HotFix+Branch+current&quot;&gt;Keep HotFix Branch current&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Merge+a+HotFix+Branch&quot;&gt;Merge a HotFix Branch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Finish+working+on+a+HotFix+or+Topic+Branch&quot;&gt;Finish working on a HotFix or Topic Branch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Create+a+New+Release&quot;&gt;Create a New Release&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Setup+New+Project&quot;&gt;Setup New Project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#Setup+to+work+on+an+existing+project&quot;&gt;Setup to work on an existing project&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;This article describes my personal &lt;code&gt;git&lt;/code&gt; Workflow.&lt;/p&gt;
&lt;h3 id=&quot;Start+working+on+a+Topic+Branch&quot; name=&quot;Start+working+on+a+Topic+Branch&quot;&gt;Start working on a Topic Branch&lt;/h3&gt;
&lt;p&gt;This when we are implementing a new feature. Assumes that you have a working git repo.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git checkout -b &quot;topic&quot; dev
git push -u origin &quot;topic&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From a different computer, you may want to work on an existing work branch.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git fetch origin
git checkout --track origin/topic
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Keep+Topic+Branch+current&quot; name=&quot;Keep+Topic+Branch+current&quot;&gt;Keep Topic Branch current&lt;/h3&gt;
&lt;p&gt;While developing a topic we may want to bring any changes done to the dev/integration test...&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git checkout topic
git merge dev
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Merge+a+Topic+Branch&quot; name=&quot;Merge+a+Topic+Branch&quot;&gt;Merge a Topic Branch&lt;/h3&gt;
&lt;p&gt;Once all the development and testing for a topic is done...&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git checkout dev
git pull
# switch to the dev (integration) branch
git merge --no-ff topic
# The --no-ff makes this a single commit.
# ... Update any changelogs and commit them...
git push
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Start+working+on+a+HotFix&quot; name=&quot;Start+working+on+a+HotFix&quot;&gt;Start working on a HotFix&lt;/h3&gt;
&lt;p&gt;This when we want to fix a prod release bug. Assumes that you have a working git repo.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git checkout -b &quot;topic&quot; master
git push -u origin &quot;topic&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From a different computer, you may want to work on an existing work branch.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git fetch origin
git checkout --track origin/topic
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Keep+HotFix+Branch+current&quot; name=&quot;Keep+HotFix+Branch+current&quot;&gt;Keep HotFix Branch current&lt;/h3&gt;
&lt;p&gt;While developing a topic we may want to bring any changes done to the dev/integration test...&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git checkout topic
git merge master
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Merge+a+HotFix+Branch&quot; name=&quot;Merge+a+HotFix+Branch&quot;&gt;Merge a HotFix Branch&lt;/h3&gt;
&lt;p&gt;Once all the development and testing for a topic is done...&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git checkout master
git pull
# switch to the dev (integration) branch
git merge --no-ff topic&amp;lt;/p&amp;gt;
# The --no-ff makes this a single comit.
# ... update any changelogs and commit them ...
git push
git checkout dev
# We also want to add changes to dev...
git merge --no-ff topic
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;.. On another system....&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git remote prune origin
git branch --delete topic
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Finish+working+on+a+HotFix+or+Topic+Branch&quot; name=&quot;Finish+working+on+a+HotFix+or+Topic+Branch&quot;&gt;Finish working on a HotFix or Topic Branch&lt;/h3&gt;
&lt;p&gt;If really done, or if you want to abort this...&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git branch -d topic
git push origin dev|master
# Use dev or master depending on being a topic branch or a hot
# fix branch respectively
git push origin :topic
# Delete the remote branch... Or ...
git push origin --delete topic
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Create+a+New+Release&quot; name=&quot;Create+a+New+Release&quot;&gt;Create a New Release&lt;/h3&gt;
&lt;p&gt;We are ready for a new release...&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git checkout dev
git push
git pull
# Make sure that dev is up-to-date in both directions...
git checkout master
git push ; git pull
# Make sure that master is up-to-date
git merge --no-ff dev
# ... fix version number ...
git commit -a -m&quot;preparing release X.Y&quot;
git tag -a X.Yrel -m&quot;Release X.Y&quot;
git push
git checkout dev
git merge --no-ff master
# ... bump version number ...
git commit -a -m&quot;Bump version to X.Y+1&quot;
git tag -a X.Y+1pre -m&quot;New dev cycle for X.Y+1&quot;
git push origin dev
git push origin --tags
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Setup+New+Project&quot; name=&quot;Setup+New+Project&quot;&gt;Setup New Project&lt;/h3&gt;
&lt;p&gt;For setting up a new project.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir project
cd project
# ... create files ...
git init
git add .
git commit -m&quot;Initial commit&quot;
git tag -a &quot;0.0initial&quot; -m &quot;Initial commit&quot;
git checkout -b &quot;dev&quot; &quot;master&quot;
git tag -a &quot;0.0pre&quot; -m &quot;Development branch&quot;
git push origin --tags&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This sets up a local repo with two branches and some descriptive tags.
The &amp;quot;master&amp;quot; branch for release code and the &amp;quot;dev&amp;quot; branch for
development and integration. We now need to configure it on the remote repository.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git checkout master
git remote add origin &quot;Remote repo URL&quot;
git push origin master
git checkout dev
git push -u origin dev
git push origin --tags
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;Setup+to+work+on+an+existing+project&quot; name=&quot;Setup+to+work+on+an+existing+project&quot;&gt;Setup to work on an existing project&lt;/h3&gt;
&lt;p&gt;Setup clone:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone &quot;Remote repo URL&quot;
git push origin master
&lt;/code&gt;&lt;/pre&gt;</content>
</entry>
</feed>