<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="../assets/xml/rss.xsl" media="all"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>akeil.de (Posts about linux)</title><link>https://akeil.de/</link><description></description><atom:link href="https://akeil.de/categories/linux.xml" rel="self" type="application/rss+xml"></atom:link><language>en</language><lastBuildDate>Sat, 02 Jan 2021 21:07:04 GMT</lastBuildDate><generator>Nikola (getnikola.com)</generator><docs>http://blogs.law.harvard.edu/tech/rss</docs><item><title>InfluxDB for TinyCore Linux</title><link>https://akeil.de/posts/influxdb-for-tinycore-linux/</link><dc:creator>Alexander Keil</dc:creator><description>&lt;div class="section" id="influxdb-for-tinycore-linux-arm"&gt;
&lt;h2&gt;InfluxDB for TinyCore Linux (ARM)&lt;/h2&gt;
&lt;dl class="field-list simple"&gt;
&lt;dt&gt;author&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;akeil&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;date&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;2017-08-13&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;version&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;1&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;This post shows how to set up &lt;a class="reference external" href="https://www.influxdata.com/time-series-platform/influxdb/"&gt;InfluxDB&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/influxdb-for-tinycore-linux/#id1" id="id2"&gt;1&lt;/a&gt; on a RaspberryPi running &lt;a class="reference external" href="http://tinycorelinux.net/"&gt;TinyCore Linux&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/influxdb-for-tinycore-linux/#id3" id="id4"&gt;2&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This is intended as part of a setup for home automation with where InfluxDB is used
as a storage backend for statistics.&lt;/p&gt;
&lt;!-- TEASER_END --&gt;
&lt;div class="section" id="tiny-core-extension"&gt;
&lt;h3&gt;Tiny Core Extension&lt;/h3&gt;
&lt;p&gt;Before we can install InfluxDB, we need to create a &lt;a class="reference external" href="http://wiki.tinycorelinux.net/wiki:creating_extensions"&gt;TinyCore Extension&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/influxdb-for-tinycore-linux/#id5" id="id6"&gt;3&lt;/a&gt; for it.&lt;/p&gt;
&lt;p&gt;Download and extract the prebuilt binary from the &lt;a class="reference external" href="https://portal.influxdata.com/downloads#influxdb"&gt;InfluxDB Downloads&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/influxdb-for-tinycore-linux/#id7" id="id8"&gt;4&lt;/a&gt; site:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_05fcbb9851974a82bfcbc5eee94554b8-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; wget https://dl.influxdata.com/influxdb/releases/influxdb-1.3.2_linux_armhf.tar.gz
&lt;a name="rest_code_05fcbb9851974a82bfcbc5eee94554b8-2"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; tar -xzf influxdb-1.3.2_linux_armhf.tar.gz
&lt;/pre&gt;&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;The download URL is different for each version.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The extracted archive creates a directory tree like this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;etc/
    influxdb/           # default config
    logrotate.d/
usr/
    bin/                # binaries
    lib/
    share/
var/
    lib/
        influxdb/
    log/
        influxdb/&lt;/pre&gt;
&lt;p&gt;Only a minimal selection of files goes into the TinyCore extension.
Create a temporary directory (&lt;code class="docutils literal"&gt;/tmp/influxdb/usr/local&lt;/code&gt;)
and place selected files there.
The complete destination directory looks like this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;/tmp/influxdb/
    usr/
        local/
            bin/            # binaries
            etc/            # sample config
            tce.installed/  # post-install script&lt;/pre&gt;
&lt;div class="section" id="binaries"&gt;
&lt;h4&gt;Binaries&lt;/h4&gt;
&lt;p&gt;From the source &lt;code class="docutils literal"&gt;usr/&lt;/code&gt; directory, only the binaries are used. This omits
the man pages (under &lt;code class="docutils literal"&gt;share/&lt;/code&gt;) and start scripts (under &lt;code class="docutils literal"&gt;lib/&lt;/code&gt;).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="configuration-file"&gt;
&lt;h4&gt;Configuration File&lt;/h4&gt;
&lt;p&gt;The InfluxDB distribution contains a default configuration file which is
the starting point for a custom configuration.&lt;/p&gt;
&lt;p&gt;Reduce cache sizes to play nice with the available memory (see
&lt;a class="reference external" href="https://docs.influxdata.com/influxdb/v1.3/administration/config/#data"&gt;InfluxDB docs&lt;/a&gt;
):&lt;/p&gt;
&lt;pre class="code ini"&gt;&lt;a name="rest_code_50607e91b9f0470984c88ecbb39c07c2-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;[data]&lt;/span&gt;
&lt;a name="rest_code_50607e91b9f0470984c88ecbb39c07c2-2"&gt;&lt;/a&gt;&lt;span class="na"&gt;cache-max-memory-size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;20971520&lt;/span&gt;
&lt;a name="rest_code_50607e91b9f0470984c88ecbb39c07c2-3"&gt;&lt;/a&gt;&lt;span class="na"&gt;cache-snapshot-memory-size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;10485760&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Disable usage statistics:&lt;/p&gt;
&lt;pre class="code ini"&gt;&lt;a name="rest_code_05ece04a73294b50ad05799b4ecf06af-1"&gt;&lt;/a&gt;&lt;span class="na"&gt;reporting-disabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;true&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Place the configuration file in the temporary &lt;code class="docutils literal"&gt;etc&lt;/code&gt; directory,
naming it &lt;code class="docutils literal"&gt;influxdb.sample.conf&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="post-install-script"&gt;
&lt;h4&gt;Post-Install Script&lt;/h4&gt;
&lt;p&gt;TinyCore supports a &lt;em&gt;tce.installed&lt;/em&gt; script which is run after the
extension was loaded. Our post-install script will&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;if no config file is present, copy the sample config
to the default location for the config file.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;create the &lt;code class="docutils literal"&gt;influxdb&lt;/code&gt; user.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;create the default data directories with correct permissions.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Create a &lt;em&gt;tce.installed&lt;/em&gt; script like this:&lt;/p&gt;
&lt;pre class="code bash"&gt;&lt;a name="rest_code_8b454fbb43f747ed8d882d78615b4e68-1"&gt;&lt;/a&gt;&lt;span class="ch"&gt;#!/bin/sh&lt;/span&gt;
&lt;a name="rest_code_8b454fbb43f747ed8d882d78615b4e68-2"&gt;&lt;/a&gt;
&lt;a name="rest_code_8b454fbb43f747ed8d882d78615b4e68-3"&gt;&lt;/a&gt;&lt;span class="nv"&gt;CFG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/local/etc/influxdb/influxdb.conf
&lt;a name="rest_code_8b454fbb43f747ed8d882d78615b4e68-4"&gt;&lt;/a&gt;&lt;span class="nv"&gt;SAMPLE_CFG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/local/etc/influxdb/influxdb.sample.conf
&lt;a name="rest_code_8b454fbb43f747ed8d882d78615b4e68-5"&gt;&lt;/a&gt;&lt;span class="nv"&gt;DATADIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/lib/influxdb
&lt;a name="rest_code_8b454fbb43f747ed8d882d78615b4e68-6"&gt;&lt;/a&gt;&lt;span class="nv"&gt;USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;influxdb
&lt;a name="rest_code_8b454fbb43f747ed8d882d78615b4e68-7"&gt;&lt;/a&gt;
&lt;a name="rest_code_8b454fbb43f747ed8d882d78615b4e68-8"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# if no config exists, use the sample config&lt;/span&gt;
&lt;a name="rest_code_8b454fbb43f747ed8d882d78615b4e68-9"&gt;&lt;/a&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; ! -e &lt;span class="nv"&gt;$CFG&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;a name="rest_code_8b454fbb43f747ed8d882d78615b4e68-10"&gt;&lt;/a&gt;    cp &lt;span class="nv"&gt;$SAMPLE_CFG&lt;/span&gt; &lt;span class="nv"&gt;$CFG&lt;/span&gt;
&lt;a name="rest_code_8b454fbb43f747ed8d882d78615b4e68-11"&gt;&lt;/a&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;a name="rest_code_8b454fbb43f747ed8d882d78615b4e68-12"&gt;&lt;/a&gt;
&lt;a name="rest_code_8b454fbb43f747ed8d882d78615b4e68-13"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# create the influxdb user&lt;/span&gt;
&lt;a name="rest_code_8b454fbb43f747ed8d882d78615b4e68-14"&gt;&lt;/a&gt;grep -q &lt;span class="nv"&gt;$USER&lt;/span&gt; /etc/passwd
&lt;a name="rest_code_8b454fbb43f747ed8d882d78615b4e68-15"&gt;&lt;/a&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt; -ne &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;a name="rest_code_8b454fbb43f747ed8d882d78615b4e68-16"&gt;&lt;/a&gt;    adduser -S -H &lt;span class="nv"&gt;$USER&lt;/span&gt;
&lt;a name="rest_code_8b454fbb43f747ed8d882d78615b4e68-17"&gt;&lt;/a&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;a name="rest_code_8b454fbb43f747ed8d882d78615b4e68-18"&gt;&lt;/a&gt;
&lt;a name="rest_code_8b454fbb43f747ed8d882d78615b4e68-19"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# create the default data directory&lt;/span&gt;
&lt;a name="rest_code_8b454fbb43f747ed8d882d78615b4e68-20"&gt;&lt;/a&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; ! -e &lt;span class="nv"&gt;$DATADIR&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;a name="rest_code_8b454fbb43f747ed8d882d78615b4e68-21"&gt;&lt;/a&gt;    mkdir &lt;span class="nv"&gt;$DATADIR&lt;/span&gt;
&lt;a name="rest_code_8b454fbb43f747ed8d882d78615b4e68-22"&gt;&lt;/a&gt;    chown &lt;span class="nv"&gt;$USER&lt;/span&gt;:nogroup &lt;span class="nv"&gt;$DATADIR&lt;/span&gt;
&lt;a name="rest_code_8b454fbb43f747ed8d882d78615b4e68-23"&gt;&lt;/a&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;The script is added to the temporary installation dir under
&lt;code class="docutils literal"&gt;tce.installed/influxdb&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="package-script"&gt;
&lt;h4&gt;Package Script&lt;/h4&gt;
&lt;p&gt;To make the process easily repeatable for new versions of InfluxDB,
create a &lt;code class="docutils literal"&gt;package.sh&lt;/code&gt; script.
The script includes all steps except downloading the distribution files
for InfluxDB.&lt;/p&gt;
&lt;pre class="code bash"&gt;&lt;a name="rest_code_44e0d2ac4d8a41c3a79d9ed8cbc13b5f-1"&gt;&lt;/a&gt;&lt;span class="ch"&gt;#!/bin/sh&lt;/span&gt;
&lt;a name="rest_code_44e0d2ac4d8a41c3a79d9ed8cbc13b5f-2"&gt;&lt;/a&gt;&lt;span class="nv"&gt;SRC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;influxdb-1.3.2-1
&lt;a name="rest_code_44e0d2ac4d8a41c3a79d9ed8cbc13b5f-3"&gt;&lt;/a&gt;&lt;span class="nv"&gt;ROOT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/influxdb/usr/local
&lt;a name="rest_code_44e0d2ac4d8a41c3a79d9ed8cbc13b5f-4"&gt;&lt;/a&gt;
&lt;a name="rest_code_44e0d2ac4d8a41c3a79d9ed8cbc13b5f-5"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# download and install required tools&lt;/span&gt;
&lt;a name="rest_code_44e0d2ac4d8a41c3a79d9ed8cbc13b5f-6"&gt;&lt;/a&gt;tce-load -w squashfs-tools
&lt;a name="rest_code_44e0d2ac4d8a41c3a79d9ed8cbc13b5f-7"&gt;&lt;/a&gt;tce-load -i squashfs-tools
&lt;a name="rest_code_44e0d2ac4d8a41c3a79d9ed8cbc13b5f-8"&gt;&lt;/a&gt;
&lt;a name="rest_code_44e0d2ac4d8a41c3a79d9ed8cbc13b5f-9"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# cleanup&lt;/span&gt;
&lt;a name="rest_code_44e0d2ac4d8a41c3a79d9ed8cbc13b5f-10"&gt;&lt;/a&gt;sudo rm -rf &lt;span class="nv"&gt;$ROOT&lt;/span&gt;
&lt;a name="rest_code_44e0d2ac4d8a41c3a79d9ed8cbc13b5f-11"&gt;&lt;/a&gt;
&lt;a name="rest_code_44e0d2ac4d8a41c3a79d9ed8cbc13b5f-12"&gt;&lt;/a&gt;mkdir -p &lt;span class="nv"&gt;$ROOT&lt;/span&gt;/bin
&lt;a name="rest_code_44e0d2ac4d8a41c3a79d9ed8cbc13b5f-13"&gt;&lt;/a&gt;cp -a &lt;span class="nv"&gt;$SRC&lt;/span&gt;/usr/bin/* &lt;span class="nv"&gt;$ROOT&lt;/span&gt;/bin
&lt;a name="rest_code_44e0d2ac4d8a41c3a79d9ed8cbc13b5f-14"&gt;&lt;/a&gt;
&lt;a name="rest_code_44e0d2ac4d8a41c3a79d9ed8cbc13b5f-15"&gt;&lt;/a&gt;mkdir -p &lt;span class="nv"&gt;$ROOT&lt;/span&gt;/etc/influxdb
&lt;a name="rest_code_44e0d2ac4d8a41c3a79d9ed8cbc13b5f-16"&gt;&lt;/a&gt;cp influxdb.conf &lt;span class="nv"&gt;$ROOT&lt;/span&gt;/etc/influxdb/influxdb.sample.conf
&lt;a name="rest_code_44e0d2ac4d8a41c3a79d9ed8cbc13b5f-17"&gt;&lt;/a&gt;
&lt;a name="rest_code_44e0d2ac4d8a41c3a79d9ed8cbc13b5f-18"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# startup script&lt;/span&gt;
&lt;a name="rest_code_44e0d2ac4d8a41c3a79d9ed8cbc13b5f-19"&gt;&lt;/a&gt;mkdir -p &lt;span class="nv"&gt;$ROOT&lt;/span&gt;/tce.installed
&lt;a name="rest_code_44e0d2ac4d8a41c3a79d9ed8cbc13b5f-20"&gt;&lt;/a&gt;cp tce.installed &lt;span class="nv"&gt;$ROOT&lt;/span&gt;/tce.installed/influxdb
&lt;a name="rest_code_44e0d2ac4d8a41c3a79d9ed8cbc13b5f-21"&gt;&lt;/a&gt;
&lt;a name="rest_code_44e0d2ac4d8a41c3a79d9ed8cbc13b5f-22"&gt;&lt;/a&gt;
&lt;a name="rest_code_44e0d2ac4d8a41c3a79d9ed8cbc13b5f-23"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# default permissions&lt;/span&gt;
&lt;a name="rest_code_44e0d2ac4d8a41c3a79d9ed8cbc13b5f-24"&gt;&lt;/a&gt;sudo chown -R root:root &lt;span class="nv"&gt;$ROOT&lt;/span&gt;
&lt;a name="rest_code_44e0d2ac4d8a41c3a79d9ed8cbc13b5f-25"&gt;&lt;/a&gt;
&lt;a name="rest_code_44e0d2ac4d8a41c3a79d9ed8cbc13b5f-26"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# special perms for startup script&lt;/span&gt;
&lt;a name="rest_code_44e0d2ac4d8a41c3a79d9ed8cbc13b5f-27"&gt;&lt;/a&gt;sudo chown root:staff &lt;span class="nv"&gt;$ROOT&lt;/span&gt;/tce.installed
&lt;a name="rest_code_44e0d2ac4d8a41c3a79d9ed8cbc13b5f-28"&gt;&lt;/a&gt;sudo chown &lt;span class="m"&gt;775&lt;/span&gt; &lt;span class="nv"&gt;$ROOT&lt;/span&gt;/tce.installed
&lt;a name="rest_code_44e0d2ac4d8a41c3a79d9ed8cbc13b5f-29"&gt;&lt;/a&gt;
&lt;a name="rest_code_44e0d2ac4d8a41c3a79d9ed8cbc13b5f-30"&gt;&lt;/a&gt;sudo chown root:staff &lt;span class="nv"&gt;$ROOT&lt;/span&gt;/tce.installed/influxdb
&lt;a name="rest_code_44e0d2ac4d8a41c3a79d9ed8cbc13b5f-31"&gt;&lt;/a&gt;sudo chmod &lt;span class="m"&gt;755&lt;/span&gt; &lt;span class="nv"&gt;$ROOT&lt;/span&gt;/tce.installed/influxdb
&lt;a name="rest_code_44e0d2ac4d8a41c3a79d9ed8cbc13b5f-32"&gt;&lt;/a&gt;
&lt;a name="rest_code_44e0d2ac4d8a41c3a79d9ed8cbc13b5f-33"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# pack and cleanup&lt;/span&gt;
&lt;a name="rest_code_44e0d2ac4d8a41c3a79d9ed8cbc13b5f-34"&gt;&lt;/a&gt;mksquashfs /tmp/influxdb /tmp/influxdb.tcz
&lt;a name="rest_code_44e0d2ac4d8a41c3a79d9ed8cbc13b5f-35"&gt;&lt;/a&gt;sudo rm -rf /tmp/influxdb
&lt;a name="rest_code_44e0d2ac4d8a41c3a79d9ed8cbc13b5f-36"&gt;&lt;/a&gt;
&lt;a name="rest_code_44e0d2ac4d8a41c3a79d9ed8cbc13b5f-37"&gt;&lt;/a&gt;mv /tmp/influxdb.tcz /etc/sysconfig/tcedir/optional/
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="configuration"&gt;
&lt;h3&gt;Configuration&lt;/h3&gt;
&lt;div class="section" id="persistence"&gt;
&lt;h4&gt;Persistence&lt;/h4&gt;
&lt;p&gt;We need to persist two locations, using &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;opt/.filetool.lst&lt;/span&gt;&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;usr/local/etc/influxdb/influxdb.conf
var/lib/influxdb&lt;/pre&gt;
&lt;p&gt;This will include the configuration file and the data directories
in the &lt;code class="docutils literal"&gt;filetool.sh&lt;/code&gt; backup (you still need to remember
to execute this before shutdown). That backup is restored during boot.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="storage"&gt;
&lt;h4&gt;Storage&lt;/h4&gt;
&lt;p&gt;The default storage location is &lt;code class="docutils literal"&gt;/var/lib/influxdb&lt;/code&gt;.
on TinyCore, this resides in memory and can be persisted using &lt;code class="docutils literal"&gt;filetool.sh&lt;/code&gt;.
Since the data files can become quite large, this is not the best option.
Both backup and restore will take a long time and the data will consume
available memory.&lt;/p&gt;
&lt;p&gt;Whether or not to place &lt;em&gt;any&lt;/em&gt; part of the database on volatile storage of course
depends on whether or not (partial) data loss is a problem.
In this case, it is assumed that this is non-critical data and that it is
acceptable if some of the data is lost if the system shuts down unexpectedly.&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;Deciding whether to store the InfluxDB on "disk" (SD-Card)
or in memory (with backup/restore on boot)
depends on the &lt;a class="reference external" href="https://docs.influxdata.com/influxdb/v1.3/concepts/storage_engine/"&gt;InfluxDB Storage Engine&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/influxdb-for-tinycore-linux/#id9" id="id10"&gt;5&lt;/a&gt;. there are two important
types of storage:&lt;/p&gt;
&lt;dl class="field-list simple"&gt;
&lt;dt&gt;WAL - Write Ahead Log&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;This is stored under &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;/var/lib/influxdb/wal/&amp;lt;database-name&amp;gt;&lt;/span&gt;&lt;/code&gt;
(the &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;wal-dir=&lt;/span&gt;&lt;/code&gt; directive from config).
New writes go into the WAL, i.e. these files receive
many writes and change often.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;TSM - Time Structured Merge Tree&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;These are &lt;em&gt;read-only&lt;/em&gt; files which contain the bulk of the data.
They are stored under &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;/var/lib/influxdb/data/&amp;lt;database-name&amp;gt;&lt;/span&gt;&lt;/code&gt;
(the &lt;code class="docutils literal"&gt;dir=&lt;/code&gt; directive from config).
During a process named &lt;em&gt;Compaction&lt;/em&gt;, data from the WAL is periodically
written to TSM files. Also during compaction, existing TSM files are
optimized, i.e. rewritten.&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/div&gt;
&lt;p&gt;The TSM files will probably become too large to keep in memory and are therefore
placed on a separate storage
(in this case, additional storage that is mounted under (&lt;code class="docutils literal"&gt;/mnt/storage&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;The WAL is kept at the default location, in memory.
This has the benefit of faster access times and also reduces writes to the RaspberryPi's
SD-card.
If the WAL is lost, we lose data from the most recent writes.&lt;/p&gt;
&lt;pre class="code ini"&gt;&lt;a name="rest_code_1cd80eccdee84e8b9d8fde5f45356f68-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;[data]&lt;/span&gt;
&lt;a name="rest_code_1cd80eccdee84e8b9d8fde5f45356f68-2"&gt;&lt;/a&gt;&lt;span class="na"&gt;...&lt;/span&gt;
&lt;a name="rest_code_1cd80eccdee84e8b9d8fde5f45356f68-3"&gt;&lt;/a&gt;&lt;span class="na"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/mnt/storage/influxdb/data"&lt;/span&gt;
&lt;a name="rest_code_1cd80eccdee84e8b9d8fde5f45356f68-4"&gt;&lt;/a&gt;&lt;span class="na"&gt;wal-dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/var/lib/influxdb/wal"&lt;/span&gt;
&lt;a name="rest_code_1cd80eccdee84e8b9d8fde5f45356f68-5"&gt;&lt;/a&gt;&lt;span class="na"&gt;...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="section" id="start-and-stop-scripts"&gt;
&lt;h4&gt;Start and Stop Scripts&lt;/h4&gt;
&lt;p&gt;Create two shell scripts to start and stop InfluxDB.&lt;/p&gt;
&lt;p&gt;The start script is included in &lt;code class="docutils literal"&gt;/opt/bootlocal.sh&lt;/code&gt;,
the stop script in &lt;code class="docutils literal"&gt;/opt/shutdown.sh&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The start script does the following:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;set permissions on pid file&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;set permissions on log file&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;start &lt;code class="docutils literal"&gt;influxd&lt;/code&gt; with parameters for pid file and configuration file
and redirect stderr to the logfile&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The stop script simply reads the process id from the pid file
and kills that process.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Start&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="code bash"&gt;&lt;a name="rest_code_a2144dafda5b4a5bb4fff8efab86ebbf-1"&gt;&lt;/a&gt;&lt;span class="nv"&gt;CONFIG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/local/etc/influxdb/influxdb.conf
&lt;a name="rest_code_a2144dafda5b4a5bb4fff8efab86ebbf-2"&gt;&lt;/a&gt;&lt;span class="nv"&gt;PIDFILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/run/influxd.pid
&lt;a name="rest_code_a2144dafda5b4a5bb4fff8efab86ebbf-3"&gt;&lt;/a&gt;&lt;span class="nv"&gt;LOGFILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/log/influxdb.log
&lt;a name="rest_code_a2144dafda5b4a5bb4fff8efab86ebbf-4"&gt;&lt;/a&gt;
&lt;a name="rest_code_a2144dafda5b4a5bb4fff8efab86ebbf-5"&gt;&lt;/a&gt;touch &lt;span class="nv"&gt;$PIDFILE&lt;/span&gt;
&lt;a name="rest_code_a2144dafda5b4a5bb4fff8efab86ebbf-6"&gt;&lt;/a&gt;chown influxdb:nogroup &lt;span class="nv"&gt;$PIDFILE&lt;/span&gt;
&lt;a name="rest_code_a2144dafda5b4a5bb4fff8efab86ebbf-7"&gt;&lt;/a&gt;
&lt;a name="rest_code_a2144dafda5b4a5bb4fff8efab86ebbf-8"&gt;&lt;/a&gt;touch &lt;span class="nv"&gt;$LOGFILE&lt;/span&gt;
&lt;a name="rest_code_a2144dafda5b4a5bb4fff8efab86ebbf-9"&gt;&lt;/a&gt;chown influxdb:staff &lt;span class="nv"&gt;$LOGFILE&lt;/span&gt;
&lt;a name="rest_code_a2144dafda5b4a5bb4fff8efab86ebbf-10"&gt;&lt;/a&gt;
&lt;a name="rest_code_a2144dafda5b4a5bb4fff8efab86ebbf-11"&gt;&lt;/a&gt;su influxdb -s /bin/sh -c &lt;span class="s2"&gt;"influxd run -pidfile &lt;/span&gt;&lt;span class="nv"&gt;$PIDFILE&lt;/span&gt;&lt;span class="s2"&gt; -config &lt;/span&gt;&lt;span class="nv"&gt;$CONFIG&lt;/span&gt;&lt;span class="s2"&gt; 2&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;$LOGFILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Stop&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="code bash"&gt;&lt;a name="rest_code_9c6028557fd54ad6a1bf314d14af8168-1"&gt;&lt;/a&gt;&lt;span class="nv"&gt;pid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;cat /var/run/influxd.pid&lt;span class="k"&gt;)&lt;/span&gt;
&lt;a name="rest_code_9c6028557fd54ad6a1bf314d14af8168-2"&gt;&lt;/a&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; -n &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$pid&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;a name="rest_code_9c6028557fd54ad6a1bf314d14af8168-3"&gt;&lt;/a&gt;    &lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$pid&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;a name="rest_code_9c6028557fd54ad6a1bf314d14af8168-4"&gt;&lt;/a&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="backup"&gt;
&lt;h3&gt;Backup&lt;/h3&gt;
&lt;p&gt;There a several options for backing up InfluxDB.
On can backup the full database(s) or only selected retention policies.
It is also possible to only back up data since a specific timestamp.&lt;/p&gt;
&lt;p&gt;The backup can be run locally or over the network
and it can be performed while InfluxDB is running.&lt;/p&gt;
&lt;p&gt;For the RaspberryPi, we create full backups, using a local cron job.
Backups are created daily and a number of the most recent backups is retained.&lt;/p&gt;
&lt;p&gt;The basic steps are as follows:
- backup the &lt;em&gt;Metastore&lt;/em&gt; and selected &lt;em&gt;Databases&lt;/em&gt; to a temporary location
- if all went well, compress the result (.tar.gz)
- delete the oldest backup&lt;/p&gt;
&lt;p&gt;To backup the &lt;strong&gt;Metastore&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class="code bash"&gt;&lt;a name="rest_code_8118269999344ef99727af8ae4a912c1-1"&gt;&lt;/a&gt;influxd backup /path/to/backup
&lt;/pre&gt;&lt;p&gt;Each &lt;strong&gt;Database&lt;/strong&gt; is backed up individually by calling&lt;/p&gt;
&lt;pre class="code bash"&gt;&lt;a name="rest_code_5b0d3e8a266d4764b84fe28fc50dabf9-1"&gt;&lt;/a&gt;influxd backup -database &amp;lt;NAME&amp;gt; /path/to/backup
&lt;/pre&gt;&lt;p&gt;See &lt;a class="reference external" href="https://docs.influxdata.com/influxdb/v1.3/administration/backup_and_restore/"&gt;InfluxDB backup and restore&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/influxdb-for-tinycore-linux/#id11" id="id12"&gt;6&lt;/a&gt; for details.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="monitoring-with-monit"&gt;
&lt;h3&gt;Monitoring with Monit&lt;/h3&gt;
&lt;p&gt;Use the following checks with &lt;a class="reference external" href="https://mmonit.com/monit/documentation/monit.html"&gt;monit&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/influxdb-for-tinycore-linux/#id13" id="id14"&gt;7&lt;/a&gt;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Check whether the process exists using the &lt;code class="docutils literal"&gt;pidfile&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make sure it is run by the &lt;code class="docutils literal"&gt;influxdb&lt;/code&gt; user&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Call the HTTP interface on the &lt;code class="docutils literal"&gt;/ping&lt;/code&gt; endpoint&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Check if there is a recent backup&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are the &lt;code class="docutils literal"&gt;monit&lt;/code&gt; checks:&lt;/p&gt;
&lt;pre class="literal-block"&gt;check process influxd pidfile /var/run/influxd.pid
    group influxdb
    if failed uid influxdb then alert
    if failed
        port 8086
        protocol http
        request /ping
        status = 204
    then alert

check file influxdbbackup path /mnt/storage/influxdb/backups/snapshot.0.tar.gz
    group backup
    if does not exist then alert
    if timestamp &amp;gt; 25 hours then alert&lt;/pre&gt;
&lt;hr class="docutils"&gt;
&lt;dl class="footnote brackets"&gt;
&lt;dt class="label" id="id1"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/influxdb-for-tinycore-linux/#id2"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://www.influxdata.com/time-series-platform/influxdb/"&gt;https://www.influxdata.com/time-series-platform/influxdb/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id3"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/influxdb-for-tinycore-linux/#id4"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://tinycorelinux.net/"&gt;http://tinycorelinux.net/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id5"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/influxdb-for-tinycore-linux/#id6"&gt;3&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://wiki.tinycorelinux.net/wiki:creating_extensions"&gt;http://wiki.tinycorelinux.net/wiki:creating_extensions&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id7"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/influxdb-for-tinycore-linux/#id8"&gt;4&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://portal.influxdata.com/downloads#influxdb"&gt;https://portal.influxdata.com/downloads#influxdb&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id9"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/influxdb-for-tinycore-linux/#id10"&gt;5&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://docs.influxdata.com/influxdb/v1.3/concepts/storage_engine/"&gt;https://docs.influxdata.com/influxdb/v1.3/concepts/storage_engine/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id11"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/influxdb-for-tinycore-linux/#id12"&gt;6&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://docs.influxdata.com/influxdb/v1.3/administration/backup_and_restore/"&gt;https://docs.influxdata.com/influxdb/v1.3/administration/backup_and_restore/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id13"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/influxdb-for-tinycore-linux/#id14"&gt;7&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://mmonit.com/monit/documentation/monit.html"&gt;https://mmonit.com/monit/documentation/monit.html&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/div&gt;
&lt;/div&gt;</description><category>homeserver</category><category>influxdb</category><category>linux</category><category>raspi</category><category>tinycore</category><guid>https://akeil.de/posts/influxdb-for-tinycore-linux/</guid><pubDate>Sat, 12 Aug 2017 22:00:00 GMT</pubDate></item><item><title>MQTT Bridge with Mosquitto and nginx</title><link>https://akeil.de/posts/mqtt-bridge-with-mosquitto-and-nginx/</link><dc:creator>Alexander Keil</dc:creator><description>&lt;div class="section" id="mqtt-bridge-with-mosquitto-and-nginx"&gt;
&lt;h2&gt;MQTT Bridge with Mosquitto and nginx&lt;/h2&gt;
&lt;dl class="field-list simple"&gt;
&lt;dt&gt;author&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;akeil&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;date&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;2017-03-05&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;version&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;1&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;Set up a &lt;a class="reference external" href="https://mosquitto.org/"&gt;Mosquitto&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/mqtt-bridge-with-mosquitto-and-nginx/#id2" id="id3"&gt;1&lt;/a&gt; MQTT broker which is available in the internet.
&lt;a class="reference external" href="https://nginx.com/"&gt;nginx&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/mqtt-bridge-with-mosquitto-and-nginx/#id4" id="id5"&gt;2&lt;/a&gt; is used as a reverse proxy and to handle SSL encryption.
An additional Mosquitto instance on the local network is used as a &lt;em&gt;bridge&lt;/em&gt;
to forward MQTT messages from the local network to the internet and vice versa.&lt;/p&gt;
&lt;p&gt;This allows to easily connect devices which do not support authentication
or encryption on the local network with with other devices or services
connected via the internet.&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="/images/mqtt-bridge-overview.png" src="https://akeil.de/images/mqtt-bridge-overview.png"&gt;
&lt;p class="caption"&gt;Overview for an MQTT bridge scenario.
Clients on the local network use an insecure connection to talk
to the local MQTT broker
while clients on the internet use a secure connection.&lt;/p&gt;
&lt;/div&gt;
&lt;!-- diagram made with draw.io --&gt;
&lt;!-- TEASER_END --&gt;
&lt;div class="section" id="mosquitto-public"&gt;
&lt;h3&gt;Mosquitto (public)&lt;/h3&gt;
&lt;p&gt;The public Mosquitto instance needs some configuration changes.
The configuration file is located at &lt;code class="docutils literal"&gt;/etc/mosquitto/mosquitto.conf&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Since we will be running behind nginx, make the MQTT service listen on
localhost only:&lt;/p&gt;
&lt;pre class="code text"&gt;&lt;a name="rest_code_bea6e6ae9f914ac8b42e7727db043b13-1"&gt;&lt;/a&gt;bind_address localhost
&lt;/pre&gt;&lt;div class="topic"&gt;
&lt;p class="topic-title"&gt;Change MQTT Service Port (optional)&lt;/p&gt;
&lt;p&gt;Change the default port number from &lt;code class="docutils literal"&gt;1883&lt;/code&gt; to something else:&lt;/p&gt;
&lt;pre class="code text"&gt;&lt;a name="rest_code_9d69e1563abc494d89dde972a0b4de1b-1"&gt;&lt;/a&gt;listener 9883
&lt;/pre&gt;&lt;p&gt;This is strictly not necessary as the default is normally used for
unencrypted connections and we will configure nginx to listen
on port &lt;code class="docutils literal"&gt;8883&lt;/code&gt; (the default port for MQTT over SSL).&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Finally, set up mosquitto to require authentication with username and password:&lt;/p&gt;
&lt;pre class="literal-block"&gt;allow_anonymous false
password_file = /etc/mosquitto/passwords&lt;/pre&gt;
&lt;p&gt;Create the password file and the first user with:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_7a1323342992457c820ed9bf094b0f7f-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;#&lt;/span&gt; mosquitto_passwd -c /etc/mosquitto/passwords username
&lt;/pre&gt;&lt;p&gt;&lt;em&gt;(You will be prompted for a password.)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;To test authentication, attempt to subscribe to any topic.
A valid username and password should work, a subscription attempt
with invalid credentials should be rejected.&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_14ecd786c93b426c8bb096f8826de167-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; mosquitto_sub -p &lt;span class="m"&gt;9883&lt;/span&gt; -t &lt;span class="s1"&gt;'#'&lt;/span&gt; -u username -P secret
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="section" id="id1"&gt;
&lt;h3&gt;nginx&lt;/h3&gt;
&lt;p&gt;Configure &lt;a class="reference external" href="https://www.nginx.com/resources/admin-guide/tcp-load-balancing/"&gt;nginx as a reverse proxy for TCP streams&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/mqtt-bridge-with-mosquitto-and-nginx/#id6" id="id7"&gt;3&lt;/a&gt;
and to &lt;a class="reference external" href="https://www.nginx.com/resources/admin-guide/nginx-tcp-ssl-termination/"&gt;terminate the SSL encryption&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/mqtt-bridge-with-mosquitto-and-nginx/#id8" id="id9"&gt;4&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;First, define Mosquitto as an &lt;em&gt;upstream&lt;/em&gt; service.
Use either &lt;code class="docutils literal"&gt;1883&lt;/code&gt; or whatever alternative port is set for the
default (unencrypted) MQTT service.&lt;/p&gt;
&lt;p&gt;Next, set up a &lt;em&gt;server&lt;/em&gt; to listen on port &lt;code class="docutils literal"&gt;8883&lt;/code&gt; (MQTT over SSL)
and tell it to pass requests to the upstream service.&lt;/p&gt;
&lt;p&gt;To enable SSL, configure the location of the SSL certificate
and private key.
Set additional SSL options as needed; in this case taken from &lt;a class="reference external" href="https://cipherli.st/"&gt;cipherli.st&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/mqtt-bridge-with-mosquitto-and-nginx/#id10" id="id11"&gt;5&lt;/a&gt;.&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;The example assumes a &lt;a class="reference external" href="https://letsencrypt.org/"&gt;Let's Encrypt&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/mqtt-bridge-with-mosquitto-and-nginx/#id12" id="id13"&gt;6&lt;/a&gt; certificate.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The nginx configuration looks like this
(with &lt;code class="docutils literal"&gt;stream&lt;/code&gt; being a toplevel-directive):&lt;/p&gt;
&lt;pre class="code nginx"&gt;&lt;a name="rest_code_dd1d0578bf2e43beb58766208846e165-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;stream&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;a name="rest_code_dd1d0578bf2e43beb58766208846e165-2"&gt;&lt;/a&gt;
&lt;a name="rest_code_dd1d0578bf2e43beb58766208846e165-3"&gt;&lt;/a&gt;    &lt;span class="kn"&gt;upstream&lt;/span&gt; &lt;span class="s"&gt;mosquitto&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;a name="rest_code_dd1d0578bf2e43beb58766208846e165-4"&gt;&lt;/a&gt;        &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="n"&gt;localhost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;9883&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a name="rest_code_dd1d0578bf2e43beb58766208846e165-5"&gt;&lt;/a&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;a name="rest_code_dd1d0578bf2e43beb58766208846e165-6"&gt;&lt;/a&gt;
&lt;a name="rest_code_dd1d0578bf2e43beb58766208846e165-7"&gt;&lt;/a&gt;    &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;a name="rest_code_dd1d0578bf2e43beb58766208846e165-8"&gt;&lt;/a&gt;        &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;8883&lt;/span&gt; &lt;span class="s"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a name="rest_code_dd1d0578bf2e43beb58766208846e165-9"&gt;&lt;/a&gt;        &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;mosquitto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a name="rest_code_dd1d0578bf2e43beb58766208846e165-10"&gt;&lt;/a&gt;
&lt;a name="rest_code_dd1d0578bf2e43beb58766208846e165-11"&gt;&lt;/a&gt;        &lt;span class="kn"&gt;ssl_certificate&lt;/span&gt; &lt;span class="s"&gt;/etc/letsencrypt/live/example.com/fullchain.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a name="rest_code_dd1d0578bf2e43beb58766208846e165-12"&gt;&lt;/a&gt;        &lt;span class="kn"&gt;ssl_certificate_key&lt;/span&gt; &lt;span class="s"&gt;/etc/letsencrypt/live/example.com/privkey.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a name="rest_code_dd1d0578bf2e43beb58766208846e165-13"&gt;&lt;/a&gt;
&lt;a name="rest_code_dd1d0578bf2e43beb58766208846e165-14"&gt;&lt;/a&gt;        &lt;span class="c1"&gt;# from https://cipherli.st/&lt;/span&gt;
&lt;a name="rest_code_dd1d0578bf2e43beb58766208846e165-15"&gt;&lt;/a&gt;        &lt;span class="kn"&gt;ssl_protocols&lt;/span&gt; &lt;span class="s"&gt;TLSv1&lt;/span&gt; &lt;span class="s"&gt;TLSv1.1&lt;/span&gt; &lt;span class="s"&gt;TLSv1.2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a name="rest_code_dd1d0578bf2e43beb58766208846e165-16"&gt;&lt;/a&gt;        &lt;span class="kn"&gt;ssl_prefer_server_ciphers&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a name="rest_code_dd1d0578bf2e43beb58766208846e165-17"&gt;&lt;/a&gt;        &lt;span class="kn"&gt;ssl_ciphers&lt;/span&gt; &lt;span class="s"&gt;"EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a name="rest_code_dd1d0578bf2e43beb58766208846e165-18"&gt;&lt;/a&gt;        &lt;span class="kn"&gt;ssl_ecdh_curve&lt;/span&gt; &lt;span class="s"&gt;secp384r1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a name="rest_code_dd1d0578bf2e43beb58766208846e165-19"&gt;&lt;/a&gt;        &lt;span class="kn"&gt;ssl_session_cache&lt;/span&gt; &lt;span class="s"&gt;shared:SSL:10m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a name="rest_code_dd1d0578bf2e43beb58766208846e165-20"&gt;&lt;/a&gt;        &lt;span class="kn"&gt;ssl_session_tickets&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a name="rest_code_dd1d0578bf2e43beb58766208846e165-21"&gt;&lt;/a&gt;
&lt;a name="rest_code_dd1d0578bf2e43beb58766208846e165-22"&gt;&lt;/a&gt;        &lt;span class="kn"&gt;ssl_dhparam&lt;/span&gt; &lt;span class="s"&gt;/etc/ssl/certs/dhparam.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a name="rest_code_dd1d0578bf2e43beb58766208846e165-23"&gt;&lt;/a&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;a name="rest_code_dd1d0578bf2e43beb58766208846e165-24"&gt;&lt;/a&gt;
&lt;a name="rest_code_dd1d0578bf2e43beb58766208846e165-25"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="section" id="mqtt-bridge"&gt;
&lt;h3&gt;MQTT-Bridge&lt;/h3&gt;
&lt;p&gt;On the local (LAN) MQTT broker, edit the configuration to add a &lt;em&gt;bridge&lt;/em&gt;:&lt;/p&gt;
&lt;pre class="code text"&gt;&lt;a name="rest_code_9ff809e2838641f5a9206e49a89787e6-1"&gt;&lt;/a&gt;connection bridge
&lt;a name="rest_code_9ff809e2838641f5a9206e49a89787e6-2"&gt;&lt;/a&gt;address &amp;lt;ip-or-hostname&amp;gt;:8883
&lt;a name="rest_code_9ff809e2838641f5a9206e49a89787e6-3"&gt;&lt;/a&gt;
&lt;a name="rest_code_9ff809e2838641f5a9206e49a89787e6-4"&gt;&lt;/a&gt;remote_username foo
&lt;a name="rest_code_9ff809e2838641f5a9206e49a89787e6-5"&gt;&lt;/a&gt;remote_password xxx
&lt;a name="rest_code_9ff809e2838641f5a9206e49a89787e6-6"&gt;&lt;/a&gt;
&lt;a name="rest_code_9ff809e2838641f5a9206e49a89787e6-7"&gt;&lt;/a&gt;remote_clientid LAN-broker
&lt;a name="rest_code_9ff809e2838641f5a9206e49a89787e6-8"&gt;&lt;/a&gt;local_client public-broker
&lt;a name="rest_code_9ff809e2838641f5a9206e49a89787e6-9"&gt;&lt;/a&gt;
&lt;a name="rest_code_9ff809e2838641f5a9206e49a89787e6-10"&gt;&lt;/a&gt;# must specify cafile to enable SSL
&lt;a name="rest_code_9ff809e2838641f5a9206e49a89787e6-11"&gt;&lt;/a&gt;bridge_cafile /etc/ssl/certs/DST_Root_CA_X3.pem
&lt;a name="rest_code_9ff809e2838641f5a9206e49a89787e6-12"&gt;&lt;/a&gt;bridge_insecure false
&lt;a name="rest_code_9ff809e2838641f5a9206e49a89787e6-13"&gt;&lt;/a&gt;
&lt;a name="rest_code_9ff809e2838641f5a9206e49a89787e6-14"&gt;&lt;/a&gt;# topic mappings
&lt;a name="rest_code_9ff809e2838641f5a9206e49a89787e6-15"&gt;&lt;/a&gt;# topic PATTERN [out|in|both] QOS LOCAL-PREFIX REMOTE-PREFIX
&lt;a name="rest_code_9ff809e2838641f5a9206e49a89787e6-16"&gt;&lt;/a&gt;topic foo both 0
&lt;/pre&gt;&lt;p&gt;Remember to specify a port with the bridge &lt;code class="docutils literal"&gt;connection&lt;/code&gt;, otherwise &lt;code class="docutils literal"&gt;1883&lt;/code&gt; is used.
The &lt;code class="docutils literal"&gt;bridge_cafile&lt;/code&gt; setting is required to enable SSL.
See &lt;a class="reference external" href="https://mosquitto.org/man/mosquitto-conf-5.html"&gt;Mosquitto conf&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/mqtt-bridge-with-mosquitto-and-nginx/#id14" id="id15"&gt;7&lt;/a&gt; for more details.&lt;/p&gt;
&lt;p&gt;Restart mosquitto to apply the new settings.&lt;/p&gt;
&lt;p&gt;The Mosquitto log file for the local broker should contain a line like this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;Connecting bridge bridge (&amp;lt;ip-or-hostname&amp;gt;:8883)&lt;/pre&gt;
&lt;p&gt;On the remote side, the bridge looks like a normal client connection:&lt;/p&gt;
&lt;pre class="literal-block"&gt;New client connected from ::1 as &amp;lt;remote_clientid&amp;gt; (c0, k60, u'&amp;lt;remote_username&amp;gt;').&lt;/pre&gt;
&lt;p&gt;To test, subscribe to &lt;code class="docutils literal"&gt;foo&lt;/code&gt; on the local broker...&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_f2ba256a31244013928c266544b31e79-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; mosquitto_sub -h &amp;lt;localbroker&amp;gt; -t foo
&lt;/pre&gt;&lt;p&gt;... and publish to &lt;code class="docutils literal"&gt;foo&lt;/code&gt; on the remote broker:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_7d892eae489548449c9bf29924721b9d-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; mosquitto_pub -h &amp;lt;remotebroker&amp;gt; -p &lt;span class="m"&gt;8883&lt;/span&gt; -u username -P secret -t foo -m &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;You should see the "test" message appear on the session subscribed
to the local broker.&lt;/p&gt;
&lt;div class="topic"&gt;
&lt;p class="topic-title"&gt;Topic Mappings&lt;/p&gt;
&lt;p&gt;A &lt;em&gt;topic mapping&lt;/em&gt; consists of a &lt;em&gt;pattern&lt;/em&gt; to subscribe to,
the direction of the messages (&lt;em&gt;in&lt;/em&gt;, &lt;em&gt;out&lt;/em&gt; or &lt;em&gt;both&lt;/em&gt;
and a &lt;em&gt;QoS&lt;/em&gt; setting.
Additionally, a &lt;em&gt;local prefix&lt;/em&gt; and &lt;em&gt;remote prefix&lt;/em&gt; can be defined.&lt;/p&gt;
&lt;p&gt;Only the &lt;em&gt;pattern&lt;/em&gt; is required.
Direction defaults to &lt;code class="docutils literal"&gt;out&lt;/code&gt;, &lt;em&gt;QoS&lt;/em&gt; defaults to &lt;code class="docutils literal"&gt;0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Examples:&lt;/p&gt;
&lt;pre class="literal-block"&gt;# forward all 'foo' messages to the bridge
topic foo
topic foo out 0  # same as above

# two-way forwarding for everything under "foo"
topic foo/# both

# (only) receive all foo messages
topic foo/# in&lt;/pre&gt;
&lt;p&gt;This can be used to forward only selected topics
and to insert topics from the bridged broker into the
local topic tree.&lt;/p&gt;
&lt;p&gt;If no &lt;code class="docutils literal"&gt;topic&lt;/code&gt; directive is present, no messages are
forwarded across the bridge.&lt;/p&gt;
&lt;/div&gt;
&lt;hr class="docutils"&gt;
&lt;dl class="footnote brackets"&gt;
&lt;dt class="label" id="id2"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/mqtt-bridge-with-mosquitto-and-nginx/#id3"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://mosquitto.org/"&gt;https://mosquitto.org/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id4"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/mqtt-bridge-with-mosquitto-and-nginx/#id5"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://nginx.com/"&gt;https://nginx.com/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id6"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/mqtt-bridge-with-mosquitto-and-nginx/#id7"&gt;3&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://www.nginx.com/resources/admin-guide/tcp-load-balancing/"&gt;https://www.nginx.com/resources/admin-guide/tcp-load-balancing/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id8"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/mqtt-bridge-with-mosquitto-and-nginx/#id9"&gt;4&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://www.nginx.com/resources/admin-guide/nginx-tcp-ssl-termination/"&gt;https://www.nginx.com/resources/admin-guide/nginx-tcp-ssl-termination/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id10"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/mqtt-bridge-with-mosquitto-and-nginx/#id11"&gt;5&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://cipherli.st/"&gt;https://cipherli.st/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id12"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/mqtt-bridge-with-mosquitto-and-nginx/#id13"&gt;6&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://letsencrypt.org/"&gt;https://letsencrypt.org/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id14"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/mqtt-bridge-with-mosquitto-and-nginx/#id15"&gt;7&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://mosquitto.org/man/mosquitto-conf-5.html"&gt;https://mosquitto.org/man/mosquitto-conf-5.html&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/div&gt;
&lt;/div&gt;</description><category>linux</category><category>mqtt</category><category>nginx</category><guid>https://akeil.de/posts/mqtt-bridge-with-mosquitto-and-nginx/</guid><pubDate>Sun, 05 Mar 2017 10:50:00 GMT</pubDate></item><item><title>Mosquitto MQTT on TinyCore Linux</title><link>https://akeil.de/posts/mosquitto-mqtt-on-tinycore/</link><dc:creator>Alexander Keil</dc:creator><description>&lt;div class="section" id="mosquitto-mqtt-on-tinycore-linux"&gt;
&lt;h2&gt;Mosquitto MQTT on TinyCore Linux&lt;/h2&gt;
&lt;dl class="field-list simple"&gt;
&lt;dt&gt;author&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;akeil&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;date&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;2017-01-14&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;version&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;1&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;This post describes how to install the &lt;a class="reference external" href="http://mosquitto.org/"&gt;Mosquitto&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/mosquitto-mqtt-on-tinycore/#id1" id="id2"&gt;1&lt;/a&gt; &lt;a class="reference external" href="http://mqtt.org/"&gt;MQTT&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/mosquitto-mqtt-on-tinycore/#id3" id="id4"&gt;2&lt;/a&gt; broker on a Raspberry Pi
with &lt;a class="reference external" href="http://tinycorelinux.net/"&gt;TinyCore&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/mosquitto-mqtt-on-tinycore/#id5" id="id6"&gt;3&lt;/a&gt; Linux.&lt;/p&gt;
&lt;!-- TEASER_END --&gt;
&lt;div class="section" id="build-an-extension"&gt;
&lt;h3&gt;Build an Extension&lt;/h3&gt;
&lt;p&gt;There is no prebuilt extension for Mosquitto which means that have to build the
program from source and create a custom extension for TinyCore Linux.&lt;/p&gt;
&lt;p&gt;First, download the source and unpack:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_f31ee193126f4b219dc12a828af02af8-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; wget http://mosquitto.org/files/source/mosquitto-1.4.10.tar.gz
&lt;a name="rest_code_f31ee193126f4b219dc12a828af02af8-2"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; tar -xzf mosquitto-1.4.10.tar.gz
&lt;/pre&gt;&lt;p&gt;Next, install build dependencies.&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_10a5734abdd34662b33b6a3708197c98-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; tce-load -w make
&lt;a name="rest_code_10a5734abdd34662b33b6a3708197c98-2"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; tce-load -w gcc
&lt;a name="rest_code_10a5734abdd34662b33b6a3708197c98-3"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; tce-load -w compiletc
&lt;a name="rest_code_10a5734abdd34662b33b6a3708197c98-4"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; tce-load -w openssl-dev
&lt;a name="rest_code_10a5734abdd34662b33b6a3708197c98-5"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; tce-load -w squashfs-tools
&lt;a name="rest_code_10a5734abdd34662b33b6a3708197c98-6"&gt;&lt;/a&gt;
&lt;a name="rest_code_10a5734abdd34662b33b6a3708197c98-7"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; tce-load -i make
&lt;a name="rest_code_10a5734abdd34662b33b6a3708197c98-8"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; tce-load -i gcc
&lt;a name="rest_code_10a5734abdd34662b33b6a3708197c98-9"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; tce-load -i compiletc
&lt;a name="rest_code_10a5734abdd34662b33b6a3708197c98-10"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; tce-load -i openssl-dev
&lt;a name="rest_code_10a5734abdd34662b33b6a3708197c98-11"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; tce-load -i squashfs-tools
&lt;/pre&gt;&lt;div class="section" id="edit-makefiles"&gt;
&lt;h4&gt;Edit Makefiles&lt;/h4&gt;
&lt;p&gt;There are several Makefiles in the Mosquitto project directory.
Some of these contain the command &lt;code class="docutils literal"&gt;install &lt;span class="pre"&gt;-s&lt;/span&gt; &lt;span class="pre"&gt;--strip-program&lt;/span&gt; foo&lt;/code&gt; but the
&lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;--strip-program&lt;/span&gt;&lt;/code&gt; option is not supported by busybox &lt;em&gt;install&lt;/em&gt;.
Remove that option (not the call)  in all affected Makefiles:&lt;/p&gt;
&lt;pre class="literal-block"&gt;./Makefile
./lib/Makefile
./lib/cpp/Makefile
./client/Makefile
./src/Makefile&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="build-and-install"&gt;
&lt;h4&gt;Build and Install&lt;/h4&gt;
&lt;p&gt;According to the Mosquitto README, optional dependencies are &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;c-ares&lt;/span&gt;&lt;/code&gt;
and &lt;code class="docutils literal"&gt;libuuid&lt;/code&gt;, both of which are not available.
So we will call &lt;code class="docutils literal"&gt;make WITH_SRV=no WITH_UUID=no&lt;/code&gt; to build.&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;The README says to use &lt;code class="docutils literal"&gt;WITH_DNS_SRV=no&lt;/code&gt; to build without &lt;em&gt;ares&lt;/em&gt;.
We need to do this if we do not want to build ares ourselves.
This is not quite correct, it is &lt;code class="docutils literal"&gt;WITH_SRV=no&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_22316e81d2db44459627938dd61b944d-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; mosquitto-1.4.10/
&lt;a name="rest_code_22316e81d2db44459627938dd61b944d-2"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; make
&lt;a name="rest_code_22316e81d2db44459627938dd61b944d-3"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; make &lt;span class="nv"&gt;WITH_SRV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;no &lt;span class="nv"&gt;WITH_UUID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;no
&lt;/pre&gt;&lt;p&gt;Install into &lt;code class="docutils literal"&gt;/tmp&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_ec671fabed224444bebfe6486436565e-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo make install &lt;span class="nv"&gt;WITH_DOCS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;no &lt;span class="nv"&gt;DESTDIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/mosquitto
&lt;/pre&gt;&lt;div class="sidebar"&gt;
&lt;p class="sidebar-title"&gt;TinyCore Extensions&lt;/p&gt;
&lt;p&gt;Information on how to create tce's can be found in the
&lt;a class="reference external" href="http://distro.ibiblio.org/tinycorelinux/corebook.pdf"&gt;TinyCore Book&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/mosquitto-mqtt-on-tinycore/#id7" id="id8"&gt;4&lt;/a&gt; [PDF] and in the &lt;a class="reference external" href="http://wiki.tinycorelinux.net/wiki:creating_extensions"&gt;TinyCore Wiki&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/mosquitto-mqtt-on-tinycore/#id9" id="id10"&gt;5&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Installation is done as root so we have the correct permissions
on all installed files.&lt;/p&gt;
&lt;p&gt;TinyCore Linux recommends to &lt;em&gt;not&lt;/em&gt; include the docs in the extension
(optionally package them in a separate -docs extension). The &lt;code class="docutils literal"&gt;WITH_DOCS=no&lt;/code&gt;
option does exactly this.&lt;/p&gt;
&lt;p&gt;The TinyCore Linux guide recommends to &lt;em&gt;strip&lt;/em&gt; the created binaries.
This has already been done in the Makefiles with &lt;code class="docutils literal"&gt;install &lt;span class="pre"&gt;-s&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;After installation, all relevant files are located in &lt;code class="docutils literal"&gt;/tmp/mosquitto&lt;/code&gt;.
Execute &lt;code class="docutils literal"&gt;/tmp/mosquitto/usr/local/sbin/mosquitto&lt;/code&gt; to make a test-run.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="create-the-extension"&gt;
&lt;h4&gt;Create the Extension&lt;/h4&gt;
&lt;p&gt;To pack the extension:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_c3f7778fa1b9442dbeffe298d8ad2d68-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; mksquashfs /tmp/mosquitto/ /tmp/mosquitto.tcz
&lt;a name="rest_code_c3f7778fa1b9442dbeffe298d8ad2d68-2"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; rm -rf /tmp/mosquitto
&lt;a name="rest_code_c3f7778fa1b9442dbeffe298d8ad2d68-3"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; mv /tmp/mosquitto.tcz /etc/sysconfig/tcedir/optional/
&lt;/pre&gt;&lt;p&gt;Then to install&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_26cbeb8c112e4d5c97ea4ca39210c1c8-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; tce-load -i mosquitto
&lt;/pre&gt;&lt;p&gt;To load on boot:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_35708bb8a89c471096a62ad7e499a8f9-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; mosquitto.tcz &amp;gt;&amp;gt; /etc/sysconfig/tcedir/onboot.lst
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="create-user"&gt;
&lt;h3&gt;Create User&lt;/h3&gt;
&lt;p&gt;To run Mosquitto with a dedicated user, create that user:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_188ad91c434b403fae577945d2fb6e15-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo adduser -S mosquitto
&lt;/pre&gt;&lt;p&gt;And edit configuration accordingly.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="configuration"&gt;
&lt;h3&gt;Configuration&lt;/h3&gt;
&lt;p&gt;The config file goes into &lt;code class="docutils literal"&gt;/usr/local/etc/mosquitto.conf&lt;/code&gt;.
This is not the default location, so we need to use the &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;--config-file&lt;/span&gt;&lt;/code&gt;
option when starting Mosquitto to tell it where to look for configuration.&lt;/p&gt;
&lt;p&gt;Of course, the config file should be included in the list of files
to be backed up and restored on boot.
Include it in &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;/opt/.filetool.lst&lt;/span&gt;&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;usr/local/etc/mosquitto.conf&lt;/pre&gt;
&lt;div class="section" id="logging"&gt;
&lt;h4&gt;Logging&lt;/h4&gt;
&lt;p&gt;TinyCore Linux does not come with a syslog daemon. If logging is desired,
write to a log file:&lt;/p&gt;
&lt;pre class="literal-block"&gt;log_dest file /var/log/mosquitto.log&lt;/pre&gt;
&lt;p&gt;On boot, make sure that the file is writable for the &lt;code class="docutils literal"&gt;mosquitto&lt;/code&gt; user.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="persistence"&gt;
&lt;h4&gt;Persistence&lt;/h4&gt;
&lt;p&gt;Mosquitto can write connection, subscription and message data to disk
and reload it on every restart.
To enable this in combination with TinyCore Linux, configure Mosquitto with persistence
and include the &lt;code class="docutils literal"&gt;mosquitto.db&lt;/code&gt; in backup/restore.&lt;/p&gt;
&lt;p&gt;Configuration:&lt;/p&gt;
&lt;pre class="literal-block"&gt;persistence true
persistence_location /var/lib/mosquitto/
persistence_file mosquitto.db
autosave_interval 1800&lt;/pre&gt;
&lt;p&gt;The &lt;code class="docutils literal"&gt;autosave_interval&lt;/code&gt; means that data is saved every 1800 seconds
(30 minutes). Additionally, state is stored on exit.&lt;/p&gt;
&lt;p&gt;The persistence directory should be created and chown`ed to &lt;code class="docutils literal"&gt;mosquitto&lt;/code&gt;
on boot.&lt;/p&gt;
&lt;p&gt;To include it in backup and restore, include it in &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;/opt/.filetool.lst&lt;/span&gt;&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;var/lib/mosquitto&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="pid-file"&gt;
&lt;h4&gt;PID File&lt;/h4&gt;
&lt;p&gt;To help with shutdown and monitoring, we let Mosquitto write a pidfile:&lt;/p&gt;
&lt;pre class="literal-block"&gt;pid_file /var/run/mosquitto.pid&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="start-on-boot"&gt;
&lt;h3&gt;Start on boot&lt;/h3&gt;
&lt;p&gt;To start the service on boot, include this in &lt;code class="docutils literal"&gt;/opt/bootlocal.sh&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_d6c7c2f72fd14373b35f2895f909e1ac-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;#&lt;/span&gt; start Mosquitto in background
&lt;a name="rest_code_d6c7c2f72fd14373b35f2895f909e1ac-2"&gt;&lt;/a&gt;&lt;span class="go"&gt;touch /var/log/mosquitto.log&lt;/span&gt;
&lt;a name="rest_code_d6c7c2f72fd14373b35f2895f909e1ac-3"&gt;&lt;/a&gt;&lt;span class="go"&gt;chown mosquitto:staff /var/log/mosquitto.log&lt;/span&gt;
&lt;a name="rest_code_d6c7c2f72fd14373b35f2895f909e1ac-4"&gt;&lt;/a&gt;&lt;span class="go"&gt;mkdir -p /var/lib/mosquitto&lt;/span&gt;
&lt;a name="rest_code_d6c7c2f72fd14373b35f2895f909e1ac-5"&gt;&lt;/a&gt;&lt;span class="go"&gt;chown mosquitto:staff /var/lib/mosquitto&lt;/span&gt;
&lt;a name="rest_code_d6c7c2f72fd14373b35f2895f909e1ac-6"&gt;&lt;/a&gt;&lt;span class="go"&gt;mosquitto --daemon --config-file /usr/local/etc/mosquitto.conf&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;This will start Mosquitto as root &lt;em&gt;unless&lt;/em&gt; the configuration file
defines the user to run as:&lt;/p&gt;
&lt;pre class="literal-block"&gt;user mosquitto&lt;/pre&gt;
&lt;p&gt;Which is recommended.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="shutdown"&gt;
&lt;h3&gt;Shutdown&lt;/h3&gt;
&lt;p&gt;To shutdown gracefully, include this in &lt;code class="docutils literal"&gt;/opt/shutdown.sh&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="code bash"&gt;&lt;a name="rest_code_476e78d71582428d884a40a59d34a9ae-1"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# stop mosquitto&lt;/span&gt;
&lt;a name="rest_code_476e78d71582428d884a40a59d34a9ae-2"&gt;&lt;/a&gt;&lt;span class="nv"&gt;mosquitto_pid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;cat /var/run/mosquitto.pid&lt;span class="k"&gt;)&lt;/span&gt;
&lt;a name="rest_code_476e78d71582428d884a40a59d34a9ae-3"&gt;&lt;/a&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; -n &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$mosquitto_pid&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;a name="rest_code_476e78d71582428d884a40a59d34a9ae-4"&gt;&lt;/a&gt;    &lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$pid&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;a name="rest_code_476e78d71582428d884a40a59d34a9ae-5"&gt;&lt;/a&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="section" id="monitoring-with-monit"&gt;
&lt;h3&gt;Monitoring with monit&lt;/h3&gt;
&lt;p&gt;To use &lt;a class="reference external" href="https://mmonit.com/monit/"&gt;monit&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/mosquitto-mqtt-on-tinycore/#id11" id="id12"&gt;6&lt;/a&gt; to monitor Mosquitto, add these checks to &lt;code class="docutils literal"&gt;monitrc&lt;/code&gt;
(or in a separate file in &lt;code class="docutils literal"&gt;monit.d/&lt;/code&gt;):&lt;/p&gt;
&lt;pre class="literal-block"&gt;check process mosquitto pidfile /var/run/mosquitto.pid
    group mqtt
    if failed uid mosquitto then alert

check program mqtt-connect
with path "/usr/local/bin/mosquitto_sub --quiet -C 1 --id monit --topic $SYS/broker/version"
    group mqtt
    if status != 0 then alert&lt;/pre&gt;
&lt;p&gt;In order to check if the broker is running, connect to it and retrieve
one message. We request a message from the &lt;code class="docutils literal"&gt;$SYS&lt;/code&gt; hierarchy and pick
one that is &lt;em&gt;static&lt;/em&gt; so that we can be sure that it exists.
The &lt;code class="docutils literal"&gt;$SYS&lt;/code&gt; hierarchy is described in the &lt;a class="reference external" href="https://mosquitto.org/man/mosquitto-8.html#idm46187459232016"&gt;Mosquitto docs&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/mosquitto-mqtt-on-tinycore/#id13" id="id14"&gt;7&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code class="docutils literal"&gt;mosquitto_sub&lt;/code&gt; comes with the Mosquitto installation an we can use it like this:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_bb0e766f59ab4b7ba9af54552b2204eb-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; mosquitto_sub --quiet -C &lt;span class="m"&gt;1&lt;/span&gt; --id monit --topic &lt;span class="se"&gt;\$&lt;/span&gt;SYS/broker/version
&lt;/pre&gt;&lt;p&gt;This should be sufficient, i.e. exit with "0" if the connection was possible.
The &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;-C&lt;/span&gt; 1&lt;/code&gt; options is necessary to exit the program after the first message.
Note that you need to escape the "$" for the command line but not in &lt;code class="docutils literal"&gt;monitrc&lt;/code&gt;.&lt;/p&gt;
&lt;hr class="docutils"&gt;
&lt;dl class="footnote brackets"&gt;
&lt;dt class="label" id="id1"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/mosquitto-mqtt-on-tinycore/#id2"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://mosquitto.org/"&gt;http://mosquitto.org/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id3"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/mosquitto-mqtt-on-tinycore/#id4"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://mqtt.org/"&gt;http://mqtt.org/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id5"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/mosquitto-mqtt-on-tinycore/#id6"&gt;3&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://tinycorelinux.net/"&gt;http://tinycorelinux.net/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id7"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/mosquitto-mqtt-on-tinycore/#id8"&gt;4&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://distro.ibiblio.org/tinycorelinux/corebook.pdf"&gt;http://distro.ibiblio.org/tinycorelinux/corebook.pdf&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id9"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/mosquitto-mqtt-on-tinycore/#id10"&gt;5&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://wiki.tinycorelinux.net/wiki:creating_extensions"&gt;http://wiki.tinycorelinux.net/wiki:creating_extensions&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id11"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/mosquitto-mqtt-on-tinycore/#id12"&gt;6&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://mmonit.com/monit/"&gt;https://mmonit.com/monit/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id13"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/mosquitto-mqtt-on-tinycore/#id14"&gt;7&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://mosquitto.org/man/mosquitto-8.html#idm46187459232016"&gt;https://mosquitto.org/man/mosquitto-8.html#idm46187459232016&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/div&gt;
&lt;/div&gt;</description><category>homeserver</category><category>linux</category><category>mqtt</category><category>raspi</category><category>tinycore</category><guid>https://akeil.de/posts/mosquitto-mqtt-on-tinycore/</guid><pubDate>Sat, 14 Jan 2017 07:37:47 GMT</pubDate></item><item><title>Node-RED on TinyCore Linux</title><link>https://akeil.de/posts/node-red-on-tinycore-linux/</link><dc:creator>Alexander Keil</dc:creator><description>&lt;div class="section" id="node-red-on-tinycore-linux"&gt;
&lt;h2&gt;Node-RED on TinyCore Linux&lt;/h2&gt;
&lt;dl class="field-list simple"&gt;
&lt;dt&gt;author&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;akeil&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;date&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;2017-01-14&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;version&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;2&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;&lt;a class="reference external" href="https://nodered.org/"&gt;Node-RED&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/node-red-on-tinycore-linux/#id1" id="id2"&gt;1&lt;/a&gt; is a flow based tool which can be used to connect services and
devices with each other and to small automation flows.&lt;/p&gt;
&lt;p&gt;Node-RED runs as a &lt;a class="reference external" href="https://nodejs.org/"&gt;NodeJS&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/node-red-on-tinycore-linux/#id3" id="id4"&gt;2&lt;/a&gt; application and is managed through the browser.&lt;/p&gt;
&lt;p&gt;This post shows how to install it on a &lt;a class="reference external" href="https://www.raspberrypi.org/"&gt;Raspberry Pi&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/node-red-on-tinycore-linux/#id5" id="id6"&gt;3&lt;/a&gt;
running &lt;a class="reference external" href="http://tinycorelinux.net/"&gt;TinyCore Linux&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/node-red-on-tinycore-linux/#id7" id="id8"&gt;4&lt;/a&gt;&lt;/p&gt;
&lt;!-- TEASER_END --&gt;
&lt;div class="section" id="installation"&gt;
&lt;h3&gt;Installation&lt;/h3&gt;
&lt;p&gt;First, install &lt;em&gt;Node.js&lt;/em&gt;:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_a8d11d16ded446e89275f7aa5cd2b215-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; tce-load -w- i node
&lt;/pre&gt;&lt;p&gt;Then install Node-RED through the &lt;a class="reference external" href="https://www.npmjs.com/"&gt;Node Package Manager&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/node-red-on-tinycore-linux/#id9" id="id10"&gt;5&lt;/a&gt; (&lt;code class="docutils literal"&gt;npm&lt;/code&gt;):&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_9c6fa93307874ed19bcb735268b02453-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo npm install -g --unsafe-perm node-red
&lt;/pre&gt;&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;The &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;-g&lt;/span&gt;&lt;/code&gt; option installs it &lt;em&gt;globally&lt;/em&gt;. The recommended procedure is to
install packages which contain executable command (like Node-RED) &lt;em&gt;globally&lt;/em&gt;
and other packages &lt;em&gt;locally&lt;/em&gt; (simply without the &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;-g&lt;/span&gt;&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;The Default location for global NPM packages on TinyCore Linux is
&lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;/usr/local/lib/node_modules/&amp;lt;package&amp;gt;&lt;/span&gt;&lt;/code&gt;.
Local packages are installed in a &lt;code class="docutils literal"&gt;node_modules&lt;/code&gt; directory
inside the current working directory.&lt;/p&gt;
&lt;p&gt;The &lt;code class="docutils literal"&gt;npm install&lt;/code&gt; command will also be used to install additional
packages (flows or nodes) for Node-RED.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Unfortunately, having a custom package manager does not go well with the
philosophy of TinyCore extensions.
Packages installed through &lt;code class="docutils literal"&gt;npm&lt;/code&gt; will not be persisted and are lost on reboot.&lt;/p&gt;
&lt;p&gt;Two things can be done to prevent this:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;install packages into a location which is backed up and restored,
e.g. one of the &lt;code class="docutils literal"&gt;/home/*&lt;/code&gt; directories.
This will be the solution for additional node packages.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create tce's for every package.
We will do this for the Node-RED package only.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To create a tce for the &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;node-red&lt;/span&gt;&lt;/code&gt; package:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_c165bdea5fa54cb691867661a03f407b-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; mkdir -p /tmp/node-red/usr/local/lib/node_modules
&lt;a name="rest_code_c165bdea5fa54cb691867661a03f407b-2"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo mv /usr/local/lib/node_modules/node-red /tmp/node-red/usr/local/lib/node_modules
&lt;a name="rest_code_c165bdea5fa54cb691867661a03f407b-3"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; mkdir -p /tmp/node-red/usr/local/bin
&lt;a name="rest_code_c165bdea5fa54cb691867661a03f407b-4"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; /tmp/node-red/usr/local/bin
&lt;a name="rest_code_c165bdea5fa54cb691867661a03f407b-5"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ln -s ../lib/node_modules/node-red/red.js node-red
&lt;a name="rest_code_c165bdea5fa54cb691867661a03f407b-6"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ln -s ../lib/node_modules/node-red/bin/node-red-pi node-red-pi
&lt;a name="rest_code_c165bdea5fa54cb691867661a03f407b-7"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; ~
&lt;a name="rest_code_c165bdea5fa54cb691867661a03f407b-8"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo chown root:root -R /tmp/node-red/
&lt;a name="rest_code_c165bdea5fa54cb691867661a03f407b-9"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; tce-load -wo squashfs-tools
&lt;a name="rest_code_c165bdea5fa54cb691867661a03f407b-10"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; tce-load -il squashfs-tools
&lt;a name="rest_code_c165bdea5fa54cb691867661a03f407b-11"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; mksquashfs /tmp/node-red/ /tmp/node-red.tcz
&lt;a name="rest_code_c165bdea5fa54cb691867661a03f407b-12"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo rm -r /tmp/node-red/
&lt;a name="rest_code_c165bdea5fa54cb691867661a03f407b-13"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; mv /tmp/node-red.tcz /etc/sysconfig/tcedir/optional/
&lt;/pre&gt;&lt;p&gt;Now, after a reboot, the node-red installation should be &lt;em&gt;gone&lt;/em&gt;.
But it can now be installed with:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_c4fe139ebf1342788a79a46824658197-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; tce-load -i node-red
&lt;/pre&gt;&lt;p&gt;To make sure that Node-RED is reinstalled on boot, add it to &lt;code class="docutils literal"&gt;onboot.lst&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_a96f20df7073435f876f1410da280b9d-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; node-red.tcz &amp;gt;&amp;gt; /etc/sysconfig/tcedir/onboot.lst
&lt;/pre&gt;&lt;p&gt;Finally, we need to secure the Node-RED configuration and data files.
These are stored in &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;$HOME/.node-red&lt;/span&gt;&lt;/code&gt; and we add it to &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;/opt/.filetool.lst&lt;/span&gt;&lt;/code&gt;.
Or rather: make sure that &lt;code class="docutils literal"&gt;home&lt;/code&gt; directories are present (they are, by default).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="additional-node-packages"&gt;
&lt;h3&gt;Additional Node Packages&lt;/h3&gt;
&lt;p&gt;Additional node packages will be installed in the &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;/home/nodered/.node-red&lt;/span&gt;&lt;/code&gt;,
assuming the the &lt;code class="docutils literal"&gt;nodered&lt;/code&gt; user will run the application.
This is done like this:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_71da5d1b9499455fac98dff314cdbc92-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; /home/nodered/.node.red
&lt;a name="rest_code_71da5d1b9499455fac98dff314cdbc92-2"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; npm install node-red-contrib-foo
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="section" id="user"&gt;
&lt;h3&gt;User&lt;/h3&gt;
&lt;p&gt;Next, we need to create the &lt;code class="docutils literal"&gt;nodered&lt;/code&gt; user which we will use to run
the service.&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_c64a4a44343e4d12a8a81a8f217a9bc0-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo adduser -S nodered
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="section" id="start-script"&gt;
&lt;h3&gt;Start Script&lt;/h3&gt;
&lt;p&gt;To start the service at boot, edit &lt;code class="docutils literal"&gt;/opt/bootlocal.sh&lt;/code&gt;.
Since Node-RED only implements logging to the console, we redirect its
output into a log file:&lt;/p&gt;
&lt;pre class="code bash"&gt;&lt;a name="rest_code_62f67ae24e5d477d9c789f263bee9c5e-1"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# start Node-RED with nodered-user in a subshell&lt;/span&gt;
&lt;a name="rest_code_62f67ae24e5d477d9c789f263bee9c5e-2"&gt;&lt;/a&gt;&lt;span class="nv"&gt;NR_LOG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/log/node-red.log
&lt;a name="rest_code_62f67ae24e5d477d9c789f263bee9c5e-3"&gt;&lt;/a&gt;touch &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NR_LOG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;a name="rest_code_62f67ae24e5d477d9c789f263bee9c5e-4"&gt;&lt;/a&gt;chown nodered:root &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NR_LOG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;a name="rest_code_62f67ae24e5d477d9c789f263bee9c5e-5"&gt;&lt;/a&gt;&lt;span class="o"&gt;(&lt;/span&gt;su nodered -c /usr/local/bin/node-red -s /bin/sh &amp;gt;&amp;gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NR_LOG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="section" id="monitoring-with-monit"&gt;
&lt;h3&gt;Monitoring (with monit)&lt;/h3&gt;
&lt;p&gt;This is how &lt;a class="reference external" href="https://mmonit.com/monit/"&gt;monit&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/node-red-on-tinycore-linux/#id11" id="id12"&gt;6&lt;/a&gt; can be used to monitor that Node-RED is up and running.
This assumes a working installation of &lt;em&gt;monit&lt;/em&gt; on TinyCore Linux.&lt;/p&gt;
&lt;p&gt;Adding checks for Node-RED is done by editing a configuration file.
The file is located in &lt;code class="docutils literal"&gt;/usr/local/etc/monit.d&lt;/code&gt;.
Actually, monit's configuration file is &lt;code class="docutils literal"&gt;/usr/local/etc/monitrc&lt;/code&gt; but it is
possible to split the configuration into several files by adding this line to
&lt;code class="docutils literal"&gt;monitrc&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;include /usr/local/etc/monit.d/*&lt;/pre&gt;
&lt;p&gt;So, inside &lt;code class="docutils literal"&gt;monit.d/&lt;/code&gt;, create a new file named &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;node-red&lt;/span&gt;&lt;/code&gt; and add
a check which looks for the &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;Node-red&lt;/span&gt;&lt;/code&gt; process and make a HTTP request
against the &lt;a class="reference external" href="http://nodered.org/docs/api/admin/"&gt;Node-RED Admin API&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/node-red-on-tinycore-linux/#id13" id="id14"&gt;7&lt;/a&gt;, specifically its &lt;code class="docutils literal"&gt;auth&lt;/code&gt; endpoint.
The &lt;code class="docutils literal"&gt;auth&lt;/code&gt; endpoint lists the currently active authentication scheme
and is available even if authentication is on.&lt;/p&gt;
&lt;p&gt;The endpoint should return either a JSON object with details on the
authentication scheme or an empty JSON object. In any case, a pair of
curly braces.&lt;/p&gt;
&lt;p&gt;We will also make sure that the application is running with the intended user.&lt;/p&gt;
&lt;p&gt;The check looks like this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;# list authentication scheme from Admin-API - see
# http://nodered.org/docs/api/admin/oauth
check process node-red matching node-red
    group node-red
    if failed
        port 1880
        with protocol http
        request /auth/login
        content = "\{.*?\}"
    then alert

    if failed uid nodered then alert&lt;/pre&gt;
&lt;hr class="docutils"&gt;
&lt;dl class="footnote brackets"&gt;
&lt;dt class="label" id="id1"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/node-red-on-tinycore-linux/#id2"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://nodered.org/"&gt;https://nodered.org/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id3"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/node-red-on-tinycore-linux/#id4"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://nodejs.org/"&gt;https://nodejs.org/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id5"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/node-red-on-tinycore-linux/#id6"&gt;3&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://www.raspberrypi.org/"&gt;https://www.raspberrypi.org/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id7"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/node-red-on-tinycore-linux/#id8"&gt;4&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://tinycorelinux.net/"&gt;http://tinycorelinux.net/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id9"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/node-red-on-tinycore-linux/#id10"&gt;5&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://www.npmjs.com/"&gt;https://www.npmjs.com/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id11"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/node-red-on-tinycore-linux/#id12"&gt;6&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://mmonit.com/monit/"&gt;https://mmonit.com/monit/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id13"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/node-red-on-tinycore-linux/#id14"&gt;7&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://nodered.org/docs/api/admin/"&gt;http://nodered.org/docs/api/admin/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/div&gt;
&lt;/div&gt;</description><category>homeserver</category><category>linux</category><category>nodejs</category><category>raspi</category><category>tinycore</category><guid>https://akeil.de/posts/node-red-on-tinycore-linux/</guid><pubDate>Thu, 12 Jan 2017 20:51:19 GMT</pubDate></item><item><title>Firefox Sync Server - Backup</title><link>https://akeil.de/posts/firefox-sync-server-backup/</link><dc:creator>Alexander Keil</dc:creator><description>&lt;div class="section" id="firefox-sync-server-backup"&gt;
&lt;h2&gt;Firefox Sync Server - Backup&lt;/h2&gt;
&lt;dl class="field-list simple"&gt;
&lt;dt&gt;author&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;akeil&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;date&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;2016-09-15&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;version&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;1&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;This is a follow up on a
&lt;a class="reference external" href="https://akeil.de/posts/firefox-sync-server-on-a-raspberry-pi/"&gt;previous post&lt;/a&gt;.
where I installed a &lt;em&gt;Firefox Sync Server&lt;/em&gt; on a Raspberry Pi running &lt;em&gt;piCore&lt;/em&gt;.
After having the Sync Service basically running, it is time to set up regular
backups.&lt;/p&gt;
&lt;!-- TEASER_END --&gt;
&lt;div class="section" id="filesystem-backup"&gt;
&lt;h3&gt;Filesystem Backup&lt;/h3&gt;
&lt;p&gt;This is a variant on a backup-scheme described in an
&lt;a class="reference external" href="https://akeil.de/posts/rolling-backup-with-rsync/"&gt;older post&lt;/a&gt;.
Most backups work by creating a backup locally and then pushing it to a remote
server. In this case there is no remote server and the backup is to be stored
on a desktop computer. The problem is that the desktop computer is not always
running.&lt;/p&gt;
&lt;p&gt;Therefore the backup will be initiated from the &lt;em&gt;destination&lt;/em&gt; machine.
It assumes that the &lt;em&gt;source&lt;/em&gt; machine(s) will always be reachable.&lt;/p&gt;
&lt;p&gt;TinyCore Linux already has a &lt;a class="reference external" href="http://wiki.tinycorelinux.net/wiki:backup"&gt;backup facility&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/firefox-sync-server-backup/#id1" id="id2"&gt;1&lt;/a&gt;, namely &lt;code class="docutils literal"&gt;filetool.sh&lt;/code&gt;.
This creates a backup of selected files under &lt;code class="docutils literal"&gt;mydata.tgz&lt;/code&gt;.
Data is restored on every boot.&lt;/p&gt;
&lt;p&gt;Use&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_3378b4b7079445c6835a4d6b48b0bd26-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; filetool.sh -bs
&lt;/pre&gt;&lt;p&gt;The &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;-s&lt;/span&gt;&lt;/code&gt; option tells filetool to create a safe backup.
"Safe" means that the previous backup is secured before it is overwritten
with a new one.&lt;/p&gt;
&lt;p&gt;Still, &lt;code class="docutils literal"&gt;mydata.tgz&lt;/code&gt; is stored in the &lt;code class="docutils literal"&gt;tce&lt;/code&gt; directory on a local drive.
And for a Raspberry Pi this means the SD card or an attached USB removable
drive. Not exactly reliable storage media.&lt;/p&gt;
&lt;p&gt;We will use &lt;a class="reference external" href="https://rsync.samba.org/"&gt;rsync&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/firefox-sync-server-backup/#id3" id="id4"&gt;2&lt;/a&gt; to periodically download data from the &lt;code class="docutils literal"&gt;tce&lt;/code&gt; directory
(and possibly other locations) to another computer.
The basic command should look like this:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_920d44b5f1d1450c92f2c2a5127fa012-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; rsync --archive rsync://box/&amp;lt;src&amp;gt; &amp;lt;dst&amp;gt;
&lt;/pre&gt;&lt;div class="section" id="install-and-configure-rsync"&gt;
&lt;h4&gt;Install and Configure rsync&lt;/h4&gt;
&lt;p&gt;Install &lt;em&gt;rsync&lt;/em&gt; on the TinyCore Linux machine with:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_342cc667232d4cad983ec702a4d9c14c-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; tce-load -wi rsync
&lt;/pre&gt;&lt;p&gt;Install with &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;-wi&lt;/span&gt;&lt;/code&gt; to have it added to the &lt;code class="docutils literal"&gt;onboot.lst&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We need rsync to run in &lt;em&gt;daemon&lt;/em&gt; mode so that the client can connect
to it at any time.&lt;/p&gt;
&lt;p&gt;First, create a minimal &lt;a class="reference external" href="https://download.samba.org/pub/rsync/rsyncd.conf.html"&gt;rsyncd configuration&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/firefox-sync-server-backup/#id5" id="id6"&gt;3&lt;/a&gt; at &lt;code class="docutils literal"&gt;/etc/rsyncd.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;uid = nobody
gid = nogroup
pid file = /var/run/rsyncd.pid
log file = /var/log/rsyncd.log
use chroot = no

[tce]
  comment = tce directory backup
  path = /mnt/mmcblk0p2/tce
  read only = yes
  uid = tc&lt;/pre&gt;
&lt;p&gt;The config defines a &lt;em&gt;module&lt;/em&gt; named &lt;code class="docutils literal"&gt;tce&lt;/code&gt;.
This means clients will see files under &lt;code class="docutils literal"&gt;/mnt/mmcblk0p2/tce&lt;/code&gt;
when they request &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;rsync://box/tce&lt;/span&gt;&lt;/code&gt;.
Since it is only intended for backup it is marked as &lt;em&gt;read only&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Add as many modules for other locations as required.&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;Remember to add &lt;code class="docutils literal"&gt;etc/rsyncd.conf&lt;/code&gt; to &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;/opt/.filetool.lst&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Start it on boot in &lt;code class="docutils literal"&gt;bootlocal.sh&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="code bash"&gt;&lt;a name="rest_code_75dbb35b0f0840e78b12aa251cc02293-1"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# start rsyncd&lt;/span&gt;
&lt;a name="rest_code_75dbb35b0f0840e78b12aa251cc02293-2"&gt;&lt;/a&gt;rsync --daemon
&lt;/pre&gt;&lt;p&gt;Stop it on shutdown in &lt;code class="docutils literal"&gt;shutdown.sh&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="code bash"&gt;&lt;a name="rest_code_dea70ed677ca4a79833e32ea7eadc911-1"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# stop rsyncd - location of pidfile from /etc/rsyncd.conf&lt;/span&gt;
&lt;a name="rest_code_dea70ed677ca4a79833e32ea7eadc911-2"&gt;&lt;/a&gt;&lt;span class="nv"&gt;pid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;cat /var/run/rsyncd.pid&lt;span class="k"&gt;)&lt;/span&gt;
&lt;a name="rest_code_dea70ed677ca4a79833e32ea7eadc911-3"&gt;&lt;/a&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; -n &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$pid&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;a name="rest_code_dea70ed677ca4a79833e32ea7eadc911-4"&gt;&lt;/a&gt;    &lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$pid&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;a name="rest_code_dea70ed677ca4a79833e32ea7eadc911-5"&gt;&lt;/a&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="section" id="the-backup-destination"&gt;
&lt;h4&gt;The Backup Destination&lt;/h4&gt;
&lt;p&gt;The actual backup task runs on the computer which is the backup &lt;em&gt;destination&lt;/em&gt;.
It will be set up so that it connects to a configured list of hosts (&lt;em&gt;sources&lt;/em&gt;)
via &lt;code class="docutils literal"&gt;rsync&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The script retrieves a list of rsync &lt;em&gt;modules&lt;/em&gt; from each host.
If a specific &lt;code class="docutils literal"&gt;KEYWORD&lt;/code&gt; (in this case: "backup") is part of the module
name or comment, all files from that module will be included in the backup.&lt;/p&gt;
&lt;p&gt;The last &lt;code class="docutils literal"&gt;KEEP&lt;/code&gt; backups are kept, the oldest backup is removed.&lt;/p&gt;
&lt;p&gt;Any host that is registered with the backup script can define any number
of rsync &lt;em&gt;modules&lt;/em&gt; and if they contain the keyword, they will be backed up.
Hosts can also use the &lt;code class="docutils literal"&gt;exclude&lt;/code&gt; option in a module definition to control
which files are backed up.&lt;/p&gt;
&lt;p&gt;On the destination this will result in a directory structure like this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;BASE/
    foo/           # backups from the "foo" host
        files.0/   # the most recent backup of "files"
            [...]
        files.1/   # an older version of "files"
            [...]
        files.2/
            [...]
        data.0/
            [...]
        data.1/
            [...]
        data.2/
            [...]
    bar/
        data.0/
        data.1/&lt;/pre&gt;
&lt;p&gt;Where we have two hosts "foo" and "bar".
The "foo" hosts has two rsync modules included in the backup
and we have collected three backups of each (0..2).
The "bar" host has only one module of which two backups were collected.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="systemd-timer-and-service"&gt;
&lt;h4&gt;Systemd Timer and Service&lt;/h4&gt;
&lt;p&gt;To control the periodic backup through &lt;em&gt;systemd&lt;/em&gt;
a &lt;code class="docutils literal"&gt;.timer&lt;/code&gt; and &lt;code class="docutils literal"&gt;.service&lt;/code&gt; file are needed.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;timer&lt;/em&gt; goes into &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;/etc/systemd/system/pi-backup.timer&lt;/span&gt;&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="code ini"&gt;&lt;a name="rest_code_e106958f9def43ffb242e23c6b597d06-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;[Unit]&lt;/span&gt;
&lt;a name="rest_code_e106958f9def43ffb242e23c6b597d06-2"&gt;&lt;/a&gt;&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Timer for Rasperry Pi backup&lt;/span&gt;
&lt;a name="rest_code_e106958f9def43ffb242e23c6b597d06-3"&gt;&lt;/a&gt;
&lt;a name="rest_code_e106958f9def43ffb242e23c6b597d06-4"&gt;&lt;/a&gt;&lt;span class="k"&gt;[Timer]&lt;/span&gt;
&lt;a name="rest_code_e106958f9def43ffb242e23c6b597d06-5"&gt;&lt;/a&gt;&lt;span class="na"&gt;OnCalendar&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;daily&lt;/span&gt;
&lt;a name="rest_code_e106958f9def43ffb242e23c6b597d06-6"&gt;&lt;/a&gt;&lt;span class="na"&gt;Persistent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;a name="rest_code_e106958f9def43ffb242e23c6b597d06-7"&gt;&lt;/a&gt;
&lt;a name="rest_code_e106958f9def43ffb242e23c6b597d06-8"&gt;&lt;/a&gt;&lt;span class="k"&gt;[Install]&lt;/span&gt;
&lt;a name="rest_code_e106958f9def43ffb242e23c6b597d06-9"&gt;&lt;/a&gt;&lt;span class="na"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;default.target&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;And the &lt;em&gt;service&lt;/em&gt; in &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;/etc/systemd/system/pi-backup.service&lt;/span&gt;&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="code ini"&gt;&lt;a name="rest_code_a970140f17ee4457ba8e2d769fbcd2b9-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;[Unit]&lt;/span&gt;
&lt;a name="rest_code_a970140f17ee4457ba8e2d769fbcd2b9-2"&gt;&lt;/a&gt;&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Backup for Raspberry Pis&lt;/span&gt;
&lt;a name="rest_code_a970140f17ee4457ba8e2d769fbcd2b9-3"&gt;&lt;/a&gt;
&lt;a name="rest_code_a970140f17ee4457ba8e2d769fbcd2b9-4"&gt;&lt;/a&gt;&lt;span class="k"&gt;[Service]&lt;/span&gt;
&lt;a name="rest_code_a970140f17ee4457ba8e2d769fbcd2b9-5"&gt;&lt;/a&gt;&lt;span class="na"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/local/bin/pi-backup.sh&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Assuming that the backup script is located at
&lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;/usr/local/bin/pi-backup.sh&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The timer is activated with:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_dd2cc9d2c5f64b10a4e82ddd529f1a8a-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; pi-backup.timer
&lt;/pre&gt;&lt;p&gt;Backups can be started manually with:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_3da35880efc744aeb751a039725bdbc5-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo systemctl start pi-backup.service
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="section" id="backup-script"&gt;
&lt;h4&gt;Backup Script&lt;/h4&gt;
&lt;p&gt;The complete script looks like this.
The UPPERCASE variables at the top of the script are meant for
configuration:&lt;/p&gt;
&lt;pre class="code bash"&gt;&lt;a name="rest_code_7388126b959f44659d947c14049e6630-1"&gt;&lt;/a&gt;&lt;span class="ch"&gt;#!/bin/sh&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-2"&gt;&lt;/a&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-3"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# basedir for all backups&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-4"&gt;&lt;/a&gt;&lt;span class="nv"&gt;ROOT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/path/to/backups
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-5"&gt;&lt;/a&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-6"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# hosts to consider for backup&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-7"&gt;&lt;/a&gt;&lt;span class="nv"&gt;HOSTS&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt; foo bar &lt;span class="o"&gt;)&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-8"&gt;&lt;/a&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-9"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# modules with KEYWORD in name or description are backed up&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-10"&gt;&lt;/a&gt;&lt;span class="nv"&gt;KEYWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;backup
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-11"&gt;&lt;/a&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-12"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# the number of old backups to keep (not including the current backup)&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-13"&gt;&lt;/a&gt;&lt;span class="nv"&gt;KEEP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-14"&gt;&lt;/a&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-15"&gt;&lt;/a&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-16"&gt;&lt;/a&gt;&lt;span class="k"&gt;function&lt;/span&gt; main &lt;span class="o"&gt;{&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-17"&gt;&lt;/a&gt;    &lt;span class="k"&gt;for&lt;/span&gt; host in &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOSTS&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-18"&gt;&lt;/a&gt;        backup_host &lt;span class="nv"&gt;$host&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-19"&gt;&lt;/a&gt;    &lt;span class="k"&gt;done&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-20"&gt;&lt;/a&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-21"&gt;&lt;/a&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-22"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# fetch a list of modules&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-23"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# filter all modules with "backup" in their description&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-24"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# args: host&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-25"&gt;&lt;/a&gt;&lt;span class="k"&gt;function&lt;/span&gt; backup_host &lt;span class="o"&gt;{&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-26"&gt;&lt;/a&gt;    &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nv"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-27"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# get a list of all modules for that host,&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-28"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# filter the ones containing "KEYWORD"&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-29"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# and keep only the module name&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-30"&gt;&lt;/a&gt;    rsync rsync://&lt;span class="nv"&gt;$host&lt;/span&gt;/ &lt;span class="p"&gt;|&lt;/span&gt; grep &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KEYWORD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; cut -f1 &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="nb"&gt;read&lt;/span&gt; -r module&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-31"&gt;&lt;/a&gt;        backup_module &lt;span class="nv"&gt;$host&lt;/span&gt; &lt;span class="nv"&gt;$module&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-32"&gt;&lt;/a&gt;    &lt;span class="k"&gt;done&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-33"&gt;&lt;/a&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-34"&gt;&lt;/a&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-35"&gt;&lt;/a&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-36"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# backup a single module&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-37"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# args: host module&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-38"&gt;&lt;/a&gt;&lt;span class="k"&gt;function&lt;/span&gt; backup_module &lt;span class="o"&gt;{&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-39"&gt;&lt;/a&gt;    &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nv"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-40"&gt;&lt;/a&gt;    &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nv"&gt;module&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-41"&gt;&lt;/a&gt;    &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nv"&gt;basedst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$ROOT&lt;/span&gt;/&lt;span class="nv"&gt;$host&lt;/span&gt;/&lt;span class="nv"&gt;$module&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-42"&gt;&lt;/a&gt;    &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$basedst&lt;/span&gt;.0
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-43"&gt;&lt;/a&gt;    &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nv"&gt;lndst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;../&lt;span class="nv"&gt;$module&lt;/span&gt;.1
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-44"&gt;&lt;/a&gt;    rotate &lt;span class="nv"&gt;$basedst&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-45"&gt;&lt;/a&gt;    &lt;span class="nb"&gt;echo&lt;/span&gt; Backup &lt;span class="nv"&gt;$host&lt;/span&gt;/&lt;span class="nv"&gt;$module&lt;/span&gt; to &lt;span class="nv"&gt;$dst&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-46"&gt;&lt;/a&gt;    mkdir -p &lt;span class="nv"&gt;$dst&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-47"&gt;&lt;/a&gt;    rsync --archive --delete --link-dest&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$lndst&lt;/span&gt; rsync://&lt;span class="nv"&gt;$host&lt;/span&gt;/&lt;span class="nv"&gt;$module&lt;/span&gt; &lt;span class="nv"&gt;$dst&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-48"&gt;&lt;/a&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-49"&gt;&lt;/a&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-50"&gt;&lt;/a&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-51"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# drop the oldest backup,&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-52"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# move other backups up one place (e.g. `backup.0` to `backup.1`)&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-53"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# args: basedst&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-54"&gt;&lt;/a&gt;&lt;span class="k"&gt;function&lt;/span&gt; rotate &lt;span class="o"&gt;{&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-55"&gt;&lt;/a&gt;    &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nv"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$KEEP&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-56"&gt;&lt;/a&gt;    &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-57"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# going backwards: n, n-1, ... 0&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-58"&gt;&lt;/a&gt;    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$counter&lt;/span&gt; -ge &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-59"&gt;&lt;/a&gt;        &lt;span class="nv"&gt;ahead&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;&lt;span class="nv"&gt;$counter&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="k"&gt;))&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-60"&gt;&lt;/a&gt;        &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nv"&gt;current&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$dst&lt;/span&gt;.&lt;span class="nv"&gt;$counter&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-61"&gt;&lt;/a&gt;        &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nv"&gt;older&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$dst&lt;/span&gt;.&lt;span class="nv"&gt;$ahead&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-62"&gt;&lt;/a&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-63"&gt;&lt;/a&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; -d &lt;span class="nv"&gt;$current&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-64"&gt;&lt;/a&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$counter&lt;/span&gt; -eq &lt;span class="nv"&gt;$KEEP&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-65"&gt;&lt;/a&gt;                &lt;span class="nb"&gt;echo&lt;/span&gt; delete &lt;span class="nv"&gt;$current&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-66"&gt;&lt;/a&gt;                rm -r &lt;span class="nv"&gt;$current&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-67"&gt;&lt;/a&gt;            &lt;span class="k"&gt;else&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-68"&gt;&lt;/a&gt;                &lt;span class="nb"&gt;echo&lt;/span&gt; move &lt;span class="nv"&gt;$current&lt;/span&gt; to &lt;span class="nv"&gt;$older&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-69"&gt;&lt;/a&gt;                mv &lt;span class="nv"&gt;$current&lt;/span&gt; &lt;span class="nv"&gt;$older&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-70"&gt;&lt;/a&gt;            &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-71"&gt;&lt;/a&gt;        &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-72"&gt;&lt;/a&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-73"&gt;&lt;/a&gt;        &lt;span class="nv"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;&lt;span class="nv"&gt;$counter&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="k"&gt;))&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-74"&gt;&lt;/a&gt;    &lt;span class="k"&gt;done&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-75"&gt;&lt;/a&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-76"&gt;&lt;/a&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-77"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# run it&lt;/span&gt;
&lt;a name="rest_code_7388126b959f44659d947c14049e6630-78"&gt;&lt;/a&gt;main
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="sql-dumps"&gt;
&lt;h3&gt;SQL Dumps&lt;/h3&gt;
&lt;p&gt;The Firefox sync server was installed using &lt;em&gt;MariaDB&lt;/em&gt; as a database backend.
We will perform &lt;em&gt;logical&lt;/em&gt; backups of the database.
That is, the backup takes the form of SQL statements which,
when executed, restore the original data.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://mariadb.com/kb/en/mariadb/mysqldump/"&gt;mysqldump&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/firefox-sync-server-backup/#id7" id="id8"&gt;4&lt;/a&gt; is used to dump the complete database into SQL statements.&lt;/p&gt;
&lt;p&gt;The backup script for the &lt;em&gt;ffsync&lt;/em&gt; database looks like this:&lt;/p&gt;
&lt;pre class="code bash"&gt;&lt;a name="rest_code_daa3deb027c74473959921ffee6c782b-1"&gt;&lt;/a&gt;&lt;span class="ch"&gt;#!/bin/sh&lt;/span&gt;
&lt;a name="rest_code_daa3deb027c74473959921ffee6c782b-2"&gt;&lt;/a&gt;&lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/mnt/storage/mysql/dumps/ffsync.dump
&lt;a name="rest_code_daa3deb027c74473959921ffee6c782b-3"&gt;&lt;/a&gt;&lt;span class="nv"&gt;cfg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/opt/ffsync.db.cnf
&lt;a name="rest_code_daa3deb027c74473959921ffee6c782b-4"&gt;&lt;/a&gt;
&lt;a name="rest_code_daa3deb027c74473959921ffee6c782b-5"&gt;&lt;/a&gt;mysqldump --defaults-extra-file&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$cfg&lt;/span&gt; --lock-tables ffsync &amp;gt; &lt;span class="nv"&gt;$dst&lt;/span&gt;.temp
&lt;a name="rest_code_daa3deb027c74473959921ffee6c782b-6"&gt;&lt;/a&gt;
&lt;a name="rest_code_daa3deb027c74473959921ffee6c782b-7"&gt;&lt;/a&gt;&lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;
&lt;a name="rest_code_daa3deb027c74473959921ffee6c782b-8"&gt;&lt;/a&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$status&lt;/span&gt; -eq &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;a name="rest_code_daa3deb027c74473959921ffee6c782b-9"&gt;&lt;/a&gt;    mv &lt;span class="nv"&gt;$dst&lt;/span&gt;.temp &lt;span class="nv"&gt;$dst&lt;/span&gt;
&lt;a name="rest_code_daa3deb027c74473959921ffee6c782b-10"&gt;&lt;/a&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;a name="rest_code_daa3deb027c74473959921ffee6c782b-11"&gt;&lt;/a&gt;
&lt;a name="rest_code_daa3deb027c74473959921ffee6c782b-12"&gt;&lt;/a&gt;&lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="nv"&gt;$status&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;The &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;--lock-tables&lt;/span&gt;&lt;/code&gt; option ensures that we retrieve a consistent
state of the database.
For InnoDB table types, &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;--single-transaction&lt;/span&gt;&lt;/code&gt; is recommended to achieve that
but table locking will work with any storage engine.&lt;/p&gt;
&lt;p&gt;To avoid having the database password in the command line,
a configuration file is used to keep credentials.
It is passed with &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;--defaults-extra-file&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The config file looks like this:&lt;/p&gt;
&lt;pre class="code ini"&gt;&lt;a name="rest_code_0fdb225e3acf48889d00561f23ee239f-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;[mysqldump]&lt;/span&gt;
&lt;a name="rest_code_0fdb225e3acf48889d00561f23ee239f-2"&gt;&lt;/a&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;ffsync&lt;/span&gt;
&lt;a name="rest_code_0fdb225e3acf48889d00561f23ee239f-3"&gt;&lt;/a&gt;&lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;secret&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Set the permissions for the config file so that only the owner can read it
and make it owned by the &lt;code class="docutils literal"&gt;mysql&lt;/code&gt; user.&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_069f181903584ccf8632ed3c0177023e-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo chown mysql:nogroup /opt/ffsync.db.cnf
&lt;a name="rest_code_069f181903584ccf8632ed3c0177023e-2"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo chmod &lt;span class="m"&gt;600&lt;/span&gt; /opt/ffsync.db.cnf
&lt;/pre&gt;&lt;p&gt;Both, script and config file are kept in &lt;code class="docutils literal"&gt;/opt&lt;/code&gt; which means they
should already be included in &lt;code class="docutils literal"&gt;filetool.lst&lt;/code&gt; for backup and restore.&lt;/p&gt;
&lt;p&gt;Finally, add the script to mysql's crontab:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_ce0d411306d94853bd49f23d7bacb992-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo crontab -eu mysql
&lt;/pre&gt;&lt;pre class="literal-block"&gt;12 1 * * * /opt/backup-ffsync-db.sh&lt;/pre&gt;
&lt;p&gt;The backup will be executed with the user we created to run the &lt;code class="docutils literal"&gt;mysqld&lt;/code&gt;
service (&lt;code class="docutils literal"&gt;mysql&lt;/code&gt;) so make sure that the backup destination is writable
for that user.&lt;/p&gt;
&lt;p&gt;To include the SQL dump in the filesystem backup,
create an rsync &lt;em&gt;module&lt;/em&gt; for it in &lt;code class="docutils literal"&gt;rsyncd.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;...
[sqldumps]
  comment = SQL dumps backup
  path = /mnt/storage/mysql/dumps
  read only = yes
  uid = mysql&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="cron"&gt;
&lt;h3&gt;Cron&lt;/h3&gt;
&lt;p&gt;We have made a &lt;code class="docutils literal"&gt;crontab&lt;/code&gt; entry but cron might not be enabled.
To enable it, we must add the &lt;a class="reference external" href="http://wiki.tinycorelinux.net/wiki:boot_codes_explained"&gt;Tiny Core bootcode&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/firefox-sync-server-backup/#id9" id="id10"&gt;5&lt;/a&gt; "&lt;code class="docutils literal"&gt;cron&lt;/code&gt;".&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Bootcodes&lt;/em&gt; can be set in a file named &lt;code class="docutils literal"&gt;cmdline.txt&lt;/code&gt;.
the file is located in the boot partition, which is normally
unmounted after boot. Mount it and edit the file:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_36453ce4bd1b418ead4613a157eb64ae-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo mount /dev/mmcblk0p1 /mnt/mmcblk0p1
&lt;a name="rest_code_36453ce4bd1b418ead4613a157eb64ae-2"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo vi /mnt/mmcblk0p1/cmdline.txt
&lt;a name="rest_code_36453ce4bd1b418ead4613a157eb64ae-3"&gt;&lt;/a&gt;&lt;span class="gp gp-VirtualEnv"&gt;(append bootcode "cron")&lt;/span&gt;
&lt;a name="rest_code_36453ce4bd1b418ead4613a157eb64ae-4"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo umount /mnt/mmcblk0p1
&lt;/pre&gt;&lt;p&gt;cmdline.txt contains a space separated list.
Simply append " cron" to the end of that list.&lt;/p&gt;
&lt;div class="admonition warning"&gt;
&lt;p class="admonition-title"&gt;Warning&lt;/p&gt;
&lt;p&gt;With &lt;em&gt;piCore&lt;/em&gt; 8.x and Raspberry Pi 3,
the file is &lt;code class="docutils literal"&gt;cmdline3.txt&lt;/code&gt;, &lt;em&gt;not&lt;/em&gt; &lt;code class="docutils literal"&gt;cmdline.txt&lt;/code&gt;.
See &lt;a class="reference external" href="http://forum.tinycorelinux.net/index.php?topic=20107.0"&gt;http://forum.tinycorelinux.net/index.php?topic=20107.0&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;After a reboot, check if it worked.
This should return the PID of the running &lt;code class="docutils literal"&gt;crond&lt;/code&gt; daemon:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_7e236664230245b3aab8a218fa7e5346-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; pgrep crond
&lt;/pre&gt;&lt;p&gt;Now make sure that &lt;em&gt;crontabs&lt;/em&gt; are persisted across boots.
Add the crontabs directory to &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;/opt/.filetool.lst&lt;/span&gt;&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;var/spool/cron/crontabs&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="timezone"&gt;
&lt;h3&gt;Timezone&lt;/h3&gt;
&lt;p&gt;If you did not set it, piCore's time zone is UTC.
And your crontab entry would refer to UTC as well.&lt;/p&gt;
&lt;p&gt;This is a good opportunity to add another bootcode (&lt;code class="docutils literal"&gt;tz&lt;/code&gt;)
for setting the timezone.&lt;/p&gt;
&lt;p&gt;The &lt;a class="reference external" href="http://wiki.tinycorelinux.net/wiki:time_zone"&gt;TZ bootcode&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/firefox-sync-server-backup/#id11" id="id12"&gt;6&lt;/a&gt; is a bit more involved it
(not as complicated as it looks, though).
For &lt;em&gt;Central European Time&lt;/em&gt; the timezone is encoded like this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;tz=CET-1CEST,M3.5.0,M10.5.0/3
   |  | |    || | | ||  | | `- 03:00:00
   |  | |    || | | ||  | `--- Sunday (day 0)
   |  | |    || | | ||  `----- Last week (week 5)
   |  | |    || | | |`-------- October (month 10)
   |  | |    || | | `--------- End of DST in format m.w.d[/h]
   |  | |    || | `----------- Sunday (day 0)
   |  | |    || `------------- Last week (week 5)
   |  | |    |`--------------- March (month 3)
   |  | |    `---------------- Begin of DST in format m.w.d[/h]
   |  | `--------------------- TZ name during DST
   |  `----------------------- UTC offset w/o DST
   `-------------------------- TZ name w/o DST&lt;/pre&gt;
&lt;p&gt;Start and end times for DST are specified in the format:&lt;/p&gt;
&lt;pre class="literal-block"&gt;Mm.w.d[/h]
 ^ ^ ^  ^
m: month, 1=January
w: week of the month (1=first, 5=last)
d: day of the week, 0=Sunday, 6=Monday
h: [optional] hour of the day, 3 for 03:00:00&lt;/pre&gt;
&lt;p&gt;If no &lt;em&gt;time&lt;/em&gt; parameter is given for DST start or end time,
02:00:00 is assumed.&lt;/p&gt;
&lt;p&gt;Add the parameter to &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;cmdline[3].txt&lt;/span&gt;&lt;/code&gt; (for CET):&lt;/p&gt;
&lt;pre class="literal-block"&gt;tz=CET-1CEST,M3.5.0,M10.5.0/3&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Whether setting the timezone this way (or setting it at all)
is a good idea is debatable. For me - living in a country with
daylight savings time - it felt more comfortable having the same
time on all computers.&lt;/em&gt;&lt;/p&gt;
&lt;hr class="docutils"&gt;
&lt;dl class="footnote brackets"&gt;
&lt;dt class="label" id="id1"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/firefox-sync-server-backup/#id2"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://wiki.tinycorelinux.net/wiki:backup"&gt;http://wiki.tinycorelinux.net/wiki:backup&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id3"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/firefox-sync-server-backup/#id4"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://rsync.samba.org/"&gt;https://rsync.samba.org/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id5"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/firefox-sync-server-backup/#id6"&gt;3&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://download.samba.org/pub/rsync/rsyncd.conf.html"&gt;https://download.samba.org/pub/rsync/rsyncd.conf.html&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id7"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/firefox-sync-server-backup/#id8"&gt;4&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://mariadb.com/kb/en/mariadb/mysqldump/"&gt;https://mariadb.com/kb/en/mariadb/mysqldump/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id9"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/firefox-sync-server-backup/#id10"&gt;5&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://wiki.tinycorelinux.net/wiki:boot_codes_explained"&gt;http://wiki.tinycorelinux.net/wiki:boot_codes_explained&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id11"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/firefox-sync-server-backup/#id12"&gt;6&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://wiki.tinycorelinux.net/wiki:time_zone"&gt;http://wiki.tinycorelinux.net/wiki:time_zone&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/div&gt;
&lt;/div&gt;</description><category>homeserver</category><category>linux</category><category>raspi</category><category>tinycore</category><guid>https://akeil.de/posts/firefox-sync-server-backup/</guid><pubDate>Thu, 15 Sep 2016 21:45:00 GMT</pubDate></item><item><title>Firefox Sync Server on a Raspberry Pi</title><link>https://akeil.de/posts/firefox-sync-server-on-a-raspberry-pi/</link><dc:creator>Alexander Keil</dc:creator><description>&lt;div class="section" id="firefox-sync-server-on-a-raspberry-pi"&gt;
&lt;h2&gt;Firefox Sync Server on a Raspberry Pi&lt;/h2&gt;
&lt;dl class="field-list simple"&gt;
&lt;dt&gt;author&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;akeil&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;date&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;2016-09-14&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;version&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;1.2&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;updated&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;2016-09-15&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;The Firefox browser can use a &lt;em&gt;Sync Service&lt;/em&gt; to synchronize settings,
bookmarks and other stuff across multiple Firefox installations.
By default, Mozilla's public sync server is used but it is possible to
&lt;a class="reference external" href="https://docs.services.mozilla.com/howtos/run-sync-1.5.html"&gt;run your own sync server&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/firefox-sync-server-on-a-raspberry-pi/#id1" id="id2"&gt;1&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We will install piCore Linux and the Firefox Sync Server on a new Raspberry Pi.&lt;/p&gt;
&lt;!-- TEASER_END --&gt;
&lt;div class="section" id="install-picore-os"&gt;
&lt;h3&gt;Install piCore OS&lt;/h3&gt;
&lt;p&gt;We will use &lt;a class="reference external" href="http://tinycorelinux.net/8.x/armv6/releases/RPi/README"&gt;piCore&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/firefox-sync-server-on-a-raspberry-pi/#id3" id="id4"&gt;2&lt;/a&gt;, the Raspberry Pi version of &lt;a class="reference external" href="http://tinycorelinux.net/"&gt;Tiny Core Linux&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/firefox-sync-server-on-a-raspberry-pi/#id5" id="id6"&gt;3&lt;/a&gt;.
The benefit of using piCore is that it runs entirely from memory
which should reduce strain on the SD card.&lt;/p&gt;
&lt;p&gt;piCore is installed by downloading the image and putting it on an SD card.&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_be9769bc0b614a9886b739f409b6d11b-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; wget http://tinycorelinux.net/8.x/armv6/releases/RPi/piCore-8.0.zip
&lt;a name="rest_code_be9769bc0b614a9886b739f409b6d11b-2"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; unzip piCore-8.0.zip
&lt;a name="rest_code_be9769bc0b614a9886b739f409b6d11b-3"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo dd &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;piCore-8.0.img &lt;span class="nv"&gt;of&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/dev/sdX &lt;span class="nv"&gt;bs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1M
&lt;a name="rest_code_be9769bc0b614a9886b739f409b6d11b-4"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo sync
&lt;/pre&gt;&lt;p&gt;Where &lt;code class="docutils literal"&gt;/dev/sdX&lt;/code&gt; is the empty SD card.
Don't forget to unmount all partitions from the SD card before writing to it.&lt;/p&gt;
&lt;p&gt;After that, put the SD card into the Raspberry Pi, plug in power and network
and log in with user &lt;code class="docutils literal"&gt;tc&lt;/code&gt; and password &lt;code class="docutils literal"&gt;piCore&lt;/code&gt;.&lt;/p&gt;
&lt;div class="section" id="extend-partition"&gt;
&lt;h4&gt;Extend Partition&lt;/h4&gt;
&lt;p&gt;TinyCore Linux knows two modes - "Cloud Mode" and "Mounted Mode".
Mounted Mode allows to have some persistence, e.g. for installed applications.
For this, a second partition is used.
The second partition is already available after installation but it should be
enlarged.&lt;/p&gt;
&lt;p&gt;Use &lt;code class="docutils literal"&gt;fdisk&lt;/code&gt; to delete the existing partition and create a new, larger one:&lt;/p&gt;
&lt;p&gt;The second partition will normally be mounted. &lt;strong&gt;Unmount&lt;/strong&gt; it first.&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_fdf6646edc0e4a3ab56621af1aedac21-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo umount /dev/mmcblk0p2
&lt;a name="rest_code_fdf6646edc0e4a3ab56621af1aedac21-2"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo fdisk -u /dev/mmcblk0
&lt;/pre&gt;&lt;p&gt;List partitions with &lt;code class="docutils literal"&gt;p&lt;/code&gt; and note down the start and end sectors of the
second partition.
Delete the second partition with &lt;code class="docutils literal"&gt;d&lt;/code&gt;, then create a new one with &lt;code class="docutils literal"&gt;n&lt;/code&gt;.
Use the same start sector as previously but different end sector.
The default end sector uses all available space.
Save with &lt;code class="docutils literal"&gt;w&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;After a reboot, log in again an expand the filesystem to fill up the second
partition:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_765faf3bc5b64addb0d3e4f7babed2b4-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo reboot
&lt;a name="rest_code_765faf3bc5b64addb0d3e4f7babed2b4-2"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo resize2fs /dev/mmcblk0p2
&lt;/pre&gt;&lt;p&gt;This should leave us with two partitions on the SD card.
A small partition 1 (&lt;code class="docutils literal"&gt;mmcblk0p1&lt;/code&gt;) which holds the OS files and will be
unmounted after boot and a larger partition 2 (&lt;code class="docutils literal"&gt;mmcblk0p2&lt;/code&gt;) which takes
most of the SD cards capacity and holds installed applications and data.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="add-additional-storage"&gt;
&lt;h4&gt;Add Additional Storage&lt;/h4&gt;
&lt;p&gt;We will use the No. 2 partition to persist applications and settings on the
SD card. We will also set up a removable USB drive to hold bulk data.
We create a filesystem on it and set it up to be mounted at boot.&lt;/p&gt;
&lt;p&gt;Plug the USB storage into the Raspberry Pi.
It should show up in &lt;code class="docutils literal"&gt;/proc/partitions&lt;/code&gt;,
&lt;code class="docutils literal"&gt;blkid&lt;/code&gt; can be used to get more info.&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_ece7c7dfdc8c4db2a309d6170a031350-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; cat /proc/partitions
&lt;a name="rest_code_ece7c7dfdc8c4db2a309d6170a031350-2"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; blkid /dev/sdX
&lt;/pre&gt;&lt;p&gt;We will put an &lt;code class="docutils literal"&gt;ext2&lt;/code&gt; filesystem on it. &lt;code class="docutils literal"&gt;ext2&lt;/code&gt; is non-journaling which
should generate less writes and &lt;a class="reference external" href="http://wiki.tinycorelinux.net/wiki:usb_life"&gt;extend the life of the flash drive&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;First, we create a single partition for the whole disk:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_aa9d03280a1c400c9381c2b38325864d-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo fdisk /dev/sdX
&lt;a name="rest_code_aa9d03280a1c400c9381c2b38325864d-2"&gt;&lt;/a&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt; d
&lt;a name="rest_code_aa9d03280a1c400c9381c2b38325864d-3"&gt;&lt;/a&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt; n
&lt;a name="rest_code_aa9d03280a1c400c9381c2b38325864d-4"&gt;&lt;/a&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt; p
&lt;a name="rest_code_aa9d03280a1c400c9381c2b38325864d-5"&gt;&lt;/a&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;a name="rest_code_aa9d03280a1c400c9381c2b38325864d-6"&gt;&lt;/a&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt; w
&lt;/pre&gt;&lt;p&gt;Then, &lt;a class="reference external" href="http://www.tldp.org/HOWTO/Flash-Memory-HOWTO/ext2.html"&gt;create an ext2 filesystem&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/firefox-sync-server-on-a-raspberry-pi/#id7" id="id8"&gt;4&lt;/a&gt;:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_98f69acb97da4522be9bb637fc209289-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; mke2fs /dev/sdX1
&lt;/pre&gt;&lt;p&gt;Since the device is a plugged in drive, we mount it by its UUID.
Find the UUID with &lt;code class="docutils literal"&gt;blkid /dev/sdX1&lt;/code&gt;.
Also we choose a different directory name for the mountpoint.&lt;/p&gt;
&lt;p&gt;To mount it automatically on boot, add the following lines
to &lt;code class="docutils literal"&gt;/opt/bootlocal.sh&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;mkdir /mnt/storage
chmod 777 /mnt/storage
mount UUID=b0ee2fe8-c54a-4c67-9a40-a9c2c4cb734c /mnt/storage&lt;/pre&gt;
&lt;p&gt;And the respective unmount command to &lt;code class="docutils literal"&gt;/opt/shutdown.sh&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;umount /mnt/storage&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="tiny-core-persistence"&gt;
&lt;h4&gt;Tiny Core Persistence&lt;/h4&gt;
&lt;p&gt;By default, TinyCore Linux has no persistence. That means all changes made
in configuration files or newly installed applications are lost when
the system reboots.
There are two options to keep data between two boots:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;backup&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;persistent partitions&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Software packages for TinyCore Linux are called "extensions" and they are stored
in the &lt;code class="docutils literal"&gt;/tce&lt;/code&gt; directory.&lt;/p&gt;
&lt;p&gt;The preconfigured image already comes with the &lt;code class="docutils literal"&gt;/tce&lt;/code&gt; directory on the second
partition so there is not much to do here.&lt;/p&gt;
&lt;p&gt;Verify that &lt;code class="docutils literal"&gt;/mnt/mmcblk0p2/tce&lt;/code&gt; exists; it should contain some preinstalled
applications (like openssh).
Look at &lt;code class="docutils literal"&gt;/etc/fstab&lt;/code&gt; to see how &lt;code class="docutils literal"&gt;/dev/mmcblk0p2&lt;/code&gt; is mounted.&lt;/p&gt;
&lt;p&gt;The backup utility compresses selected files or directories and stores them
as &lt;code class="docutils literal"&gt;mydata.tgz&lt;/code&gt; inside the &lt;code class="docutils literal"&gt;/tce&lt;/code&gt; directory.&lt;/p&gt;
&lt;p&gt;The file &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;/opt/.filetool.lst&lt;/span&gt;&lt;/code&gt; defines which files or directories will be
included. &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;/opt/.xfiletool.lst&lt;/span&gt;&lt;/code&gt; can be used to exclude previously included
files (e.g. include a complete directory tree but exclude single large files).&lt;/p&gt;
&lt;p&gt;The default list includes the &lt;code class="docutils literal"&gt;/opt&lt;/code&gt; and &lt;code class="docutils literal"&gt;/home&lt;/code&gt; directories, ssh host keys
and files related to user management (&lt;code class="docutils literal"&gt;/etc/passwd&lt;/code&gt; for example).&lt;/p&gt;
&lt;div class="admonition warning"&gt;
&lt;p class="admonition-title"&gt;Warning&lt;/p&gt;
&lt;p&gt;The backup script does &lt;strong&gt;not&lt;/strong&gt; run automatically.
One must include a call to &lt;code class="docutils literal"&gt;filetool.sh &lt;span class="pre"&gt;-b&lt;/span&gt;&lt;/code&gt; in &lt;code class="docutils literal"&gt;/opt/shutdown.sh&lt;/code&gt;.
Also, &lt;code class="docutils literal"&gt;shutdown.sh&lt;/code&gt; will only run when the system is shut down
with &lt;code class="docutils literal"&gt;exitcheck.sh&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="install-firefox-sync"&gt;
&lt;h3&gt;Install Firefox Sync&lt;/h3&gt;
&lt;p&gt;The server is installed into a python &lt;a class="reference external" href="https://virtualenv.pypa.io/en/stable/"&gt;virtualenv&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/firefox-sync-server-on-a-raspberry-pi/#id9" id="id10"&gt;5&lt;/a&gt;
which is located in the syncserver installation directory.
The server will later run as a &lt;a class="reference external" href="https://trypyramid.com/"&gt;Pyramid&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/firefox-sync-server-on-a-raspberry-pi/#id11" id="id12"&gt;6&lt;/a&gt; app
inside a &lt;a class="reference external" href="http://gunicorn.org/"&gt;Gunicorn&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/firefox-sync-server-on-a-raspberry-pi/#id13" id="id14"&gt;7&lt;/a&gt; application server.&lt;/p&gt;
&lt;p&gt;There is no prebuilt package for Firefox Sync on piCore so we need to download
the source and build the software locally.&lt;/p&gt;
&lt;div class="section" id="install-prerequisites"&gt;
&lt;h4&gt;Install Prerequisites&lt;/h4&gt;
&lt;p&gt;Start by installing prerequisites:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_e0da1175981a449fb61be94509a227dd-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; tce-load -wi python
&lt;a name="rest_code_e0da1175981a449fb61be94509a227dd-2"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; tce-load -wo python-dev
&lt;a name="rest_code_e0da1175981a449fb61be94509a227dd-3"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; tce-load -wo make
&lt;a name="rest_code_e0da1175981a449fb61be94509a227dd-4"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; tce-load -wo git
&lt;a name="rest_code_e0da1175981a449fb61be94509a227dd-5"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; tce-load -wo gcc
&lt;a name="rest_code_e0da1175981a449fb61be94509a227dd-6"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; tce-load -wo compiletc
&lt;a name="rest_code_e0da1175981a449fb61be94509a227dd-7"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; tce-load -i python-dev make git gcc compiletc
&lt;/pre&gt;&lt;p&gt;The &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;-wi&lt;/span&gt;&lt;/code&gt; switch adds the extension to the &lt;em&gt;OnBoot&lt;/em&gt; list which means
it is loaded on every boot.
The &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;-wo&lt;/span&gt;&lt;/code&gt; switch creates an &lt;em&gt;OnDemand&lt;/em&gt; item which we can load manually
with &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;tce-load&lt;/span&gt; &lt;span class="pre"&gt;-i&lt;/span&gt;&lt;/code&gt;.
We install only the base &lt;code class="docutils literal"&gt;python&lt;/code&gt; with the &lt;em&gt;OnBoot&lt;/em&gt; option because it
is the only package we need for running the app.
All other packages are only required for the build process.&lt;/p&gt;
&lt;p&gt;The installation additionally requires Python &lt;code class="docutils literal"&gt;virtualenv&lt;/code&gt;
for which we do not have a piCore package. But we can
&lt;a class="reference external" href="https://virtualenv.pypa.io/en/stable/installation/"&gt;install virtualenv from source&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/firefox-sync-server-on-a-raspberry-pi/#id15" id="id16"&gt;8&lt;/a&gt;:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_83bfcb7bfd67405c8dcc6395ba822a56-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; wget https://pypi.python.org/packages/8b/2c/c0d3e47709d0458816167002e1aa3d64d03bdeb2a9d57c5bd18448fd24cd/virtualenv-15.0.3.tar.gz
&lt;a name="rest_code_83bfcb7bfd67405c8dcc6395ba822a56-2"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; tar -xzf virtualenv-15.0.3.tar.gz
&lt;a name="rest_code_83bfcb7bfd67405c8dcc6395ba822a56-3"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; virtualenv-15.0.3
&lt;a name="rest_code_83bfcb7bfd67405c8dcc6395ba822a56-4"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo python setup.py install
&lt;a name="rest_code_83bfcb7bfd67405c8dcc6395ba822a56-5"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; ..
&lt;a name="rest_code_83bfcb7bfd67405c8dcc6395ba822a56-6"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; rm virtualenv-15.0.3.tar.gz
&lt;a name="rest_code_83bfcb7bfd67405c8dcc6395ba822a56-7"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; rm -r virtualenv-15.0.3
&lt;/pre&gt;&lt;p&gt;The download URL can be obtained from
&lt;a class="reference external" href="https://pypi.python.org/pypi/virtualenv"&gt;PyPi&lt;/a&gt;.&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;Installing virtualenv in this way places installation files in their
default locations. They will be lost on shutdown.
This does not matter (much) as we only need &lt;code class="docutils literal"&gt;virtualenv&lt;/code&gt; for the
installation, not for running the application.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="create-an-ffsync-user"&gt;
&lt;h4&gt;Create an ffsync User&lt;/h4&gt;
&lt;p&gt;We want to run the sync service under a dedicated &lt;code class="docutils literal"&gt;ffsync&lt;/code&gt; user.
Create it like so:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_f0601efbd3b440b7ad46aaab22f49590-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo adduser ffsync -SH
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="section" id="installation-location"&gt;
&lt;h4&gt;Installation Location&lt;/h4&gt;
&lt;p&gt;Decide upon an installation location.&lt;/p&gt;
&lt;p&gt;The default installation procedure is to clone the &lt;a class="reference external" href="https://github.com/mozilla-services/syncserver"&gt;syncserver git repository&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/firefox-sync-server-on-a-raspberry-pi/#id17" id="id18"&gt;9&lt;/a&gt;
and build the application inside the repo directory.
Building the application entails creation of a virtual Python environment.
The &lt;em&gt;virtualenv&lt;/em&gt; is basically a copy of the system-wide
Python install plus additional packages installed in a local directory,
in this case the syncserver installation directory.&lt;/p&gt;
&lt;p&gt;This results in a large number of files and directories.
If we keep the installation inside the &lt;code class="docutils literal"&gt;/home&lt;/code&gt; directory,
we have the benefit of running it from memory but the disadvantage
that the backup and restore for the home directory takes quite long.&lt;/p&gt;
&lt;p&gt;If we install to a persistent partition, we lose the "running from
memory" advantage.&lt;/p&gt;
&lt;p&gt;The proper(?) solution is to create a TinyCore Linux extension for it.
This is described in detail in chapter 14 and 15 of the &lt;a class="reference external" href="http://tinycorelinux.net/corebook.pdf"&gt;Tiny Core book&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/firefox-sync-server-on-a-raspberry-pi/#id19" id="id20"&gt;10&lt;/a&gt;
[PDF].
The basic idea is to create the desired directory structure and files
in some temporary directory and then "pack" it with &lt;code class="docutils literal"&gt;squashfs&lt;/code&gt;.
TinyCore Linux will later mount/symlink it into the desired location.&lt;/p&gt;
&lt;p&gt;That is, if we want to have a file &lt;code class="docutils literal"&gt;/usr/share/myfile&lt;/code&gt;,
create &lt;code class="docutils literal"&gt;/tmp/myextension/usr/share/myfile&lt;/code&gt; and then use squashfs
to create &lt;code class="docutils literal"&gt;myextension.tcz&lt;/code&gt; from &lt;code class="docutils literal"&gt;/tmp/myextension&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The syncserver build process (more precisely: &lt;code class="docutils literal"&gt;virtualenv&lt;/code&gt;) will
generate some files with hardcoded paths so it is tricky to move the installation
to some place different than where it was created.
There is an option to make a &lt;a class="reference external" href="https://virtualenv.pypa.io/en/stable/userguide/#making-environments-relocatable"&gt;relocatable virtualenv&lt;/a&gt;
but it does not seem fully supported and we are not going to use it.&lt;/p&gt;
&lt;p&gt;The good thing is that the &lt;em&gt;virtualenv&lt;/em&gt; installation keeps all required files
under a single base directory. This makes it possible to install the application
at the desired location then pack it up and remove the original install.&lt;/p&gt;
&lt;p&gt;We will install under &lt;code class="docutils literal"&gt;/usr/local/syncserver&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;First create the required directory and clone the syncserver repo into it:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_4e78aab34eac4bc6b3329c22394eacc2-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; /usr/local
&lt;a name="rest_code_4e78aab34eac4bc6b3329c22394eacc2-2"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo mkdir syncserver
&lt;a name="rest_code_4e78aab34eac4bc6b3329c22394eacc2-3"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo chown syncserver tc:staff
&lt;a name="rest_code_4e78aab34eac4bc6b3329c22394eacc2-4"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; git clone --depth&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; https://github.com/mozilla-services/syncserver syncserver
&lt;a name="rest_code_4e78aab34eac4bc6b3329c22394eacc2-5"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; syncserver
&lt;a name="rest_code_4e78aab34eac4bc6b3329c22394eacc2-6"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; make build
&lt;/pre&gt;&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;Root permissions are only required to create a new directory in &lt;code class="docutils literal"&gt;/usr/local&lt;/code&gt;.
The installation itself works with normal permissions.&lt;/p&gt;
&lt;p&gt;Use &lt;code class="docutils literal"&gt;git clone&lt;/code&gt; with &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;--depth=1&lt;/span&gt;&lt;/code&gt; to retrieve as little history as possible,
but still keep it as a git repo.&lt;/p&gt;
&lt;p&gt;Delete the &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;syncserver/.git&lt;/span&gt;&lt;/code&gt; directory to save additional space.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Next, we need to install the Python database driver.
This is done using the Python package manager (&lt;code class="docutils literal"&gt;pip&lt;/code&gt;) that is part of the
&lt;em&gt;virtualenv&lt;/em&gt;.
So, from the syncserver directory:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_1d87889e6ffd4300ada165fb0d486b13-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ./local/bin/pip install pysqlite
&lt;/pre&gt;&lt;p&gt;Install &lt;code class="docutils literal"&gt;pysqlite&lt;/code&gt; for Usage with SQLite, &lt;code class="docutils literal"&gt;PyMySQL&lt;/code&gt; for MySQL/MariaDB.&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;The &lt;code class="docutils literal"&gt;make build&lt;/code&gt; and the installation of pysqlite/PyMySQL with &lt;code class="docutils literal"&gt;pip&lt;/code&gt;
will leave several files in &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;~/.cache/pip&lt;/span&gt;&lt;/code&gt;.
You may want to delete those so they will not be included in the backup.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;We should now have a complete installation at &lt;code class="docutils literal"&gt;/usr/share/syncserver&lt;/code&gt;.
Running &lt;code class="docutils literal"&gt;sudo make serve&lt;/code&gt; from that directory should start the server
with default settings on port 5000.&lt;/p&gt;
&lt;p&gt;Look at &lt;code class="docutils literal"&gt;./local/bin/gunicorn&lt;/code&gt;. On the top it should say:&lt;/p&gt;
&lt;pre class="literal-block"&gt;#!/usr/local/syncserver/local/bin/python2&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="config"&gt;
&lt;h4&gt;Config&lt;/h4&gt;
&lt;p&gt;The default setup assumes that the syncserver is started from inside the
installation directory with &lt;code class="docutils literal"&gt;make serve&lt;/code&gt; and that the configuration file
is kept there (it is referenced with &lt;code class="docutils literal"&gt;./syncserver.ini&lt;/code&gt;).
If we want to change config separately from the installation, we need to
copy it to another location:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_9de08a01cde74614b4f18edf38215327-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; cp syncserver.ini /etc
&lt;/pre&gt;&lt;p&gt;And also include it in &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;/opt/.filetool.lst&lt;/span&gt;&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;etc/syncserver.ini&lt;/pre&gt;
&lt;p&gt;We will later use a custom command like this to run the service:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_b22b746c8ab84300a090339e11193e78-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ./local/bin/gunicorn --paste /etc/syncserver.ini
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="section" id="create-the-extension"&gt;
&lt;h4&gt;Create the Extension&lt;/h4&gt;
&lt;p&gt;First off, install &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;squashfs-tools&lt;/span&gt;&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_a63eea15c2164ccb81c3865791e165a9-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; tce-load -wo squashfs-tools
&lt;/pre&gt;&lt;p&gt;To produce a TinyCore Linux extension, we need to transfer everything
to a temporary directory structure, pack it and move the
resulting &lt;code class="docutils literal"&gt;.tcz&lt;/code&gt; file to the &lt;code class="docutils literal"&gt;tce&lt;/code&gt; directory:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_0f831404a81f453eb9ce6d168b575258-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; /tmp
&lt;a name="rest_code_0f831404a81f453eb9ce6d168b575258-2"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; mkdir -p syncserver/usr/local
&lt;a name="rest_code_0f831404a81f453eb9ce6d168b575258-3"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo mv /usr/local/syncserver syncserver/usr/local
&lt;a name="rest_code_0f831404a81f453eb9ce6d168b575258-4"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo chown root:root -R syncserver/
&lt;a name="rest_code_0f831404a81f453eb9ce6d168b575258-5"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; mksquashfs syncserver syncserver.tcz
&lt;a name="rest_code_0f831404a81f453eb9ce6d168b575258-6"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; mv syncserver.tcz /etc/sysconfig/tcedir/optional
&lt;a name="rest_code_0f831404a81f453eb9ce6d168b575258-7"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo rm -r syncserver/
&lt;/pre&gt;&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;It is a good idea to place a backup of the &lt;code class="docutils literal"&gt;syncserver.tcz&lt;/code&gt;
file somewhere other then the &lt;code class="docutils literal"&gt;tce/optional&lt;/code&gt; directory.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;We can now start the application with:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_89df97756a6244d2a1b9eb076dc8eb57-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; tce-load -i syncserver
&lt;/pre&gt;&lt;p&gt;You should see the syncserver installation (as a collection of symlinks)
under &lt;code class="docutils literal"&gt;/usr/local/syncserver&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Note that &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;tce-run&lt;/span&gt;&lt;/code&gt; will not actually start the service. It will only
make the installed application available.
Running &lt;code class="docutils literal"&gt;sudo make serve&lt;/code&gt; from the installation directory should
now work as before.&lt;/p&gt;
&lt;p&gt;To have the application available on boot, add it to the &lt;code class="docutils literal"&gt;onboot.lst&lt;/code&gt;
(inside the &lt;code class="docutils literal"&gt;tce&lt;/code&gt; directory):&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_9683093bdb6a432a9ece64088d76ba0c-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; syncserver.tcz &amp;gt;&amp;gt; /etc/sysconfig/tcedir/onboot.lst
&lt;/pre&gt;&lt;!-- TODO: md5sum? --&gt;
&lt;/div&gt;
&lt;div class="section" id="start-and-stop-script"&gt;
&lt;h4&gt;Start and Stop Script&lt;/h4&gt;
&lt;p&gt;We need a custom script to start the service.
The start script is included in &lt;code class="docutils literal"&gt;bootlocal.sh&lt;/code&gt; to start the service
on boot and performs three tasks:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;change into the correct working directory (&lt;code class="docutils literal"&gt;/usr/local/syncserver&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;switch to the &lt;code class="docutils literal"&gt;ffsync&lt;/code&gt; user&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;start the syncserver with our configuration file at &lt;code class="docutils literal"&gt;/etc/syncserver.ini&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="code bash"&gt;&lt;a name="rest_code_829996ab7e22481397a0f49a16976188-1"&gt;&lt;/a&gt;&lt;span class="nv"&gt;BASEDIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/local/syncserver
&lt;a name="rest_code_829996ab7e22481397a0f49a16976188-2"&gt;&lt;/a&gt;&lt;span class="nv"&gt;GUNICORN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$BASEDIR&lt;/span&gt;/local/bin/gunicorn
&lt;a name="rest_code_829996ab7e22481397a0f49a16976188-3"&gt;&lt;/a&gt;&lt;span class="nv"&gt;CONFIG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/etc/syncserver.ini
&lt;a name="rest_code_829996ab7e22481397a0f49a16976188-4"&gt;&lt;/a&gt;&lt;span class="nv"&gt;COMMAND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GUNICORN&lt;/span&gt;&lt;span class="s2"&gt; --paste &lt;/span&gt;&lt;span class="nv"&gt;$CONFIG&lt;/span&gt;&lt;span class="s2"&gt; --log-file /tmp/syncserver.log &amp;gt; /dev/null &amp;amp;"&lt;/span&gt;
&lt;a name="rest_code_829996ab7e22481397a0f49a16976188-5"&gt;&lt;/a&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="nv"&gt;$BASEDIR&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; su ffsync -c &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$COMMAND&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; -s /bin/sh&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;We still need to change the working directory before we execute the sync server.
If we do this inside the &lt;code class="docutils literal"&gt;bootlocal.sh&lt;/code&gt; script we might affect subsequent actions
in that script. &lt;a class="reference external" href="https://stackoverflow.com/questions/786376/how-do-i-run-a-program-with-a-different-working-directory-from-current-from-lin#786419"&gt;One way&lt;/a&gt;
to avoid this, invoke everything in a subshell.&lt;/p&gt;
&lt;p&gt;Also, since the &lt;code class="docutils literal"&gt;gunicorn&lt;/code&gt; command will not exit,
"disown" it afterwards with "&amp;amp;".&lt;/p&gt;
&lt;p&gt;To stop the service, use:&lt;/p&gt;
&lt;pre class="code bash"&gt;&lt;a name="rest_code_a71b7933cc0e42c1b25efaa0cf6b65f2-1"&gt;&lt;/a&gt;pkill syncserver
&lt;/pre&gt;&lt;p&gt;(not pretty)&lt;/p&gt;
&lt;!-- TODO: look into the ``pserve`` based solution here
http://www.raspberry-pi-geek.com/Archive/2015/11/The-new-Firefox-synchronizer --&gt;
&lt;/div&gt;
&lt;div class="section" id="configuration"&gt;
&lt;h4&gt;Configuration&lt;/h4&gt;
&lt;p&gt;Edit the config file to reflect your settings:&lt;/p&gt;
&lt;pre class="code ini"&gt;&lt;a name="rest_code_7ad709ee06aa4ea9b325c485d9b4f428-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;[syncserver]&lt;/span&gt;
&lt;a name="rest_code_7ad709ee06aa4ea9b325c485d9b4f428-2"&gt;&lt;/a&gt;&lt;span class="na"&gt;public_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;http://box:5000/&lt;/span&gt;
&lt;a name="rest_code_7ad709ee06aa4ea9b325c485d9b4f428-3"&gt;&lt;/a&gt;&lt;span class="na"&gt;sqluri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;sqlite:////mnt/storage/syncserver/syncserver.db&lt;/span&gt;
&lt;a name="rest_code_7ad709ee06aa4ea9b325c485d9b4f428-4"&gt;&lt;/a&gt;&lt;span class="na"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;xxx&lt;/span&gt;
&lt;a name="rest_code_7ad709ee06aa4ea9b325c485d9b4f428-5"&gt;&lt;/a&gt;&lt;span class="na"&gt;allow_new_users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;false  # set to true to create initial user&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;&lt;code class="docutils literal"&gt;public_url&lt;/code&gt; is set to what the client sees as the URL of the service.&lt;/p&gt;
&lt;p&gt;If you do not configure a &lt;code class="docutils literal"&gt;sqluri&lt;/code&gt;, the default applies - which is an
in-memory database (which is lost when the service is shut down).&lt;/p&gt;
&lt;p&gt;The &lt;code class="docutils literal"&gt;secret&lt;/code&gt; is used to generate authentication tokens.
It can be any random string. Generate one like this:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_be72a401e1144bebbc6635a2a5aefe09-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; head -c20 /dev/urandom &lt;span class="p"&gt;|&lt;/span&gt; sha1sum
&lt;/pre&gt;&lt;p&gt;&lt;code class="docutils literal"&gt;allow_new_users&lt;/code&gt; should be set to &lt;code class="docutils literal"&gt;true&lt;/code&gt; initially.
When the service is fully installed and all required Firefox accounts are
registered, it can be set to &lt;code class="docutils literal"&gt;false&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="install-a-database"&gt;
&lt;h3&gt;Install a Database&lt;/h3&gt;
&lt;p&gt;The syncserver works with SQLite, MySQL/MariaDB or PostgreSQL.
There are piCore packages for SQLite and MariaDB.&lt;/p&gt;
&lt;div class="section" id="sqlite"&gt;
&lt;h4&gt;SQLite&lt;/h4&gt;
&lt;p&gt;For SQLite, make sure the &lt;code class="docutils literal"&gt;sqlite3&lt;/code&gt; package is installed.
Then decide where the database file should live and update
&lt;code class="docutils literal"&gt;syncserver.ini&lt;/code&gt; accordingly:&lt;/p&gt;
&lt;pre class="code ini"&gt;&lt;a name="rest_code_a2e4e1e8b39249658b25e6f47a505c52-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;[syncserver]&lt;/span&gt;
&lt;a name="rest_code_a2e4e1e8b39249658b25e6f47a505c52-2"&gt;&lt;/a&gt;&lt;span class="na"&gt;...&lt;/span&gt;
&lt;a name="rest_code_a2e4e1e8b39249658b25e6f47a505c52-3"&gt;&lt;/a&gt;&lt;span class="na"&gt;sqluri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;sqlite:////mnt/storage/syncserver/syncserver.db&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Remember to make the database directory accessible for the
&lt;code class="docutils literal"&gt;ffsync&lt;/code&gt; user.&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_3aad0e4cb8a14780a019f5aaa02de739-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo mkdir /mnt/storage/syncserver
&lt;a name="rest_code_3aad0e4cb8a14780a019f5aaa02de739-2"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo chown ffsync:staff /mnt/storage/syncserver
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="section" id="mariadb"&gt;
&lt;h4&gt;MariaDB&lt;/h4&gt;
&lt;p&gt;There is a (somewhat outdated?) &lt;a class="reference external" href="http://wiki.tinycorelinux.net/wiki:mysql_persistence_guide"&gt;MariaDB installation guide&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/firefox-sync-server-on-a-raspberry-pi/#id21" id="id22"&gt;11&lt;/a&gt; in the TinyCore Linux wiki.&lt;/p&gt;
&lt;p&gt;Start by installing MariaDB&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_8f4e3f468d5d498ea8a3e40132d15e77-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; tce-load -wi mariadb
&lt;a name="rest_code_8f4e3f468d5d498ea8a3e40132d15e77-2"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; tce-load -wi mariadb-client
&lt;/pre&gt;&lt;p&gt;Next, we make sure that the DB service starts on boot and stops on shutdown.
The package comes only with a partially complete configuration and some effort
is required to make it work.&lt;/p&gt;
&lt;p&gt;Start by creating a system account to run mariadb:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_ed7b01c589994f6c88c767b9c251df66-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo adduser -SH mysql
&lt;/pre&gt;&lt;p&gt;Next, add commands to &lt;a class="reference external" href="https://mariadb.com/kb/en/mariadb/starting-and-stopping-mariadb-automatically/"&gt;start and stop MariaDB&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/firefox-sync-server-on-a-raspberry-pi/#id23" id="id24"&gt;12&lt;/a&gt; to &lt;code class="docutils literal"&gt;bootlocal.sh&lt;/code&gt;
and &lt;code class="docutils literal"&gt;shutdown.sh&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;# bootlocal.sh
/usr/local/share/mysql/mysql.server start

# shutdown.sh
/usr/local/share/mysql/mysql.server stop&lt;/pre&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;Make sure that the database service is started before the syncserver,
and shut down after the syncserver.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Then create a minimal &lt;a class="reference external" href="https://mariadb.com/kb/en/mariadb/configuring-mariadb-with-mycnf/"&gt;configuration file for MariaDB&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/firefox-sync-server-on-a-raspberry-pi/#id25" id="id26"&gt;13&lt;/a&gt;
file at &lt;code class="docutils literal"&gt;/etc/my.cnf&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="code ini"&gt;&lt;a name="rest_code_6a87e1783be244eeb258c8f1170d9094-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;[client]&lt;/span&gt;
&lt;a name="rest_code_6a87e1783be244eeb258c8f1170d9094-2"&gt;&lt;/a&gt;&lt;span class="na"&gt;socket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/tmp/mysql.sock&lt;/span&gt;
&lt;a name="rest_code_6a87e1783be244eeb258c8f1170d9094-3"&gt;&lt;/a&gt;
&lt;a name="rest_code_6a87e1783be244eeb258c8f1170d9094-4"&gt;&lt;/a&gt;&lt;span class="k"&gt;[mysqld]&lt;/span&gt;
&lt;a name="rest_code_6a87e1783be244eeb258c8f1170d9094-5"&gt;&lt;/a&gt;&lt;span class="na"&gt;socket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/tmp/mysql.sock&lt;/span&gt;
&lt;a name="rest_code_6a87e1783be244eeb258c8f1170d9094-6"&gt;&lt;/a&gt;&lt;span class="na"&gt;basedir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/usr/local&lt;/span&gt;
&lt;a name="rest_code_6a87e1783be244eeb258c8f1170d9094-7"&gt;&lt;/a&gt;&lt;span class="na"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;mysql&lt;/span&gt;
&lt;a name="rest_code_6a87e1783be244eeb258c8f1170d9094-8"&gt;&lt;/a&gt;&lt;span class="na"&gt;datadir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/usr/local/mysql/data&lt;/span&gt;
&lt;a name="rest_code_6a87e1783be244eeb258c8f1170d9094-9"&gt;&lt;/a&gt;&lt;span class="na"&gt;tmpdir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/tmp&lt;/span&gt;
&lt;a name="rest_code_6a87e1783be244eeb258c8f1170d9094-10"&gt;&lt;/a&gt;&lt;span class="na"&gt;log_error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/tmp/mysql.error.log&lt;/span&gt;
&lt;a name="rest_code_6a87e1783be244eeb258c8f1170d9094-11"&gt;&lt;/a&gt;&lt;span class="na"&gt;pid_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/tmp/mysql.pid&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;It is important to set the &lt;code class="docutils literal"&gt;socket&lt;/code&gt; option in the &lt;code class="docutils literal"&gt;[server]&lt;/code&gt;
&lt;em&gt;and&lt;/em&gt; &lt;code class="docutils literal"&gt;[client]&lt;/code&gt; sections. Otherwise tools like &lt;code class="docutils literal"&gt;mysqladmin&lt;/code&gt; will not
see the socket option and will fail to connect.&lt;/p&gt;
&lt;p&gt;Make sure that the &lt;code class="docutils literal"&gt;datadir&lt;/code&gt; is accessible for the &lt;code class="docutils literal"&gt;mysql&lt;/code&gt; user:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_6c1042eb5f6342a8aa3b84773c21602d-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo chown -R mysql:nogroup /usr/local/mysql/data
&lt;/pre&gt;&lt;!-- TODO Also pidfile, socket and log file(s). --&gt;
&lt;p&gt;Manually starting and stopping &lt;code class="docutils literal"&gt;mysqld&lt;/code&gt; should now work:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_790d7b19104f442787faecd1b69a2ca3-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo /usr/local/share/mysql/mysql.server start
&lt;a name="rest_code_790d7b19104f442787faecd1b69a2ca3-2"&gt;&lt;/a&gt;&lt;span class="go"&gt;Starting MySQL. SUCCESS!&lt;/span&gt;
&lt;a name="rest_code_790d7b19104f442787faecd1b69a2ca3-3"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo /usr/local/share/mysql/mysql.server stop
&lt;a name="rest_code_790d7b19104f442787faecd1b69a2ca3-4"&gt;&lt;/a&gt;&lt;span class="go"&gt;Shutting down MySQL.. SUCCESS!&lt;/span&gt;
&lt;/pre&gt;&lt;div class="section" id="persistence"&gt;
&lt;h5&gt;Persistence&lt;/h5&gt;
&lt;p&gt;For now, the config file and data directory only exist in memory.
To persist the config file between reboots, add it to the backup list
in &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;/opt/.filetool.lst&lt;/span&gt;&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;etc/my.cnf&lt;/pre&gt;
&lt;p&gt;The data directory is where the database files are stored.
By default, this is &lt;code class="docutils literal"&gt;/usr/local/mysql/data&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If this would be kept in memory and backed up/restored when the system
restarts, we have a risk of losing data when the system shuts down
unexpectedly and the backup script does not run
(e.g. when power is unplugged).&lt;/p&gt;
&lt;p&gt;To avoid this, keep the database on a separate partition
and create a symlink in the original location which points
to the new location.
Stop the &lt;code class="docutils literal"&gt;mysqld&lt;/code&gt; service before moving the data directory.&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_62e2f20452d746b884dab1251ca2b4dd-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo /usr/local/share/mysql/mysql.server stop
&lt;a name="rest_code_62e2f20452d746b884dab1251ca2b4dd-2"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo mkdir /mnt/storage/mysql
&lt;a name="rest_code_62e2f20452d746b884dab1251ca2b4dd-3"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo chown mysql:nogroup /mnt/storage/mysql
&lt;a name="rest_code_62e2f20452d746b884dab1251ca2b4dd-4"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo mv /usr/local/mysql/data /mnt/storage/mysql
&lt;a name="rest_code_62e2f20452d746b884dab1251ca2b4dd-5"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo ln -s /mnt/storage/mysql/data /usr/local/mysql
&lt;a name="rest_code_62e2f20452d746b884dab1251ca2b4dd-6"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo /usr/local/share/mysql/mysql.server start
&lt;/pre&gt;&lt;p&gt;We need to repeat parts of this on every reboot. So add the following
to &lt;code class="docutils literal"&gt;bootlocal.sh&lt;/code&gt; (&lt;em&gt;before&lt;/em&gt; starting mysqld):&lt;/p&gt;
&lt;pre class="code bash"&gt;&lt;a name="rest_code_c045ea19c47747e985b9efe8b02cbf10-1"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# replace the standard datadir with persisted data&lt;/span&gt;
&lt;a name="rest_code_c045ea19c47747e985b9efe8b02cbf10-2"&gt;&lt;/a&gt;&lt;span class="nv"&gt;MYSQL_DATADIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/local/mysql/data
&lt;a name="rest_code_c045ea19c47747e985b9efe8b02cbf10-3"&gt;&lt;/a&gt;rm -rf &lt;span class="nv"&gt;$MYSQL_DATADIR&lt;/span&gt;
&lt;a name="rest_code_c045ea19c47747e985b9efe8b02cbf10-4"&gt;&lt;/a&gt;ln -s /mnt/storage/mysql/data &lt;span class="nv"&gt;$MYSQL_DATADIR&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="section" id="setup-a-database-user"&gt;
&lt;h5&gt;Setup a Database User&lt;/h5&gt;
&lt;p&gt;First, set a password for mysql admin&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_3482bd7b13a642518bb6d3dce832575d-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; mysqladmin -u root password &amp;lt;secret&amp;gt;
&lt;/pre&gt;&lt;p&gt;Login to mysql console to create the user and database for &lt;code class="docutils literal"&gt;ffsync&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_73c9f3a6ee274048b386cada0c3787ff-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; mysql -u root -p
&lt;a name="rest_code_73c9f3a6ee274048b386cada0c3787ff-2"&gt;&lt;/a&gt;&lt;span class="gp gp-VirtualEnv"&gt;(enter password when prompted)&lt;/span&gt;
&lt;a name="rest_code_73c9f3a6ee274048b386cada0c3787ff-3"&gt;&lt;/a&gt;&lt;span class="go"&gt;mysql&amp;gt; CREATE USER 'ffsync'@'localhost' IDENTIFIED BY '&amp;lt;secret&amp;gt;';&lt;/span&gt;
&lt;a name="rest_code_73c9f3a6ee274048b386cada0c3787ff-4"&gt;&lt;/a&gt;&lt;span class="go"&gt;mysql&amp;gt; CREATE DATABASE ffsync;&lt;/span&gt;
&lt;a name="rest_code_73c9f3a6ee274048b386cada0c3787ff-5"&gt;&lt;/a&gt;&lt;span class="go"&gt;mysql&amp;gt; GRANT ALL ON ffsync.* TO 'ffsync'@'localhost';&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Finally, configure Firefox Sync Server to use the MariaDB database:&lt;/p&gt;
&lt;pre class="code ini"&gt;&lt;a name="rest_code_99ecc6c7bf7e4564a1228117aa850b7f-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;[syncserver]&lt;/span&gt;
&lt;a name="rest_code_99ecc6c7bf7e4564a1228117aa850b7f-2"&gt;&lt;/a&gt;&lt;span class="na"&gt;...&lt;/span&gt;
&lt;a name="rest_code_99ecc6c7bf7e4564a1228117aa850b7f-3"&gt;&lt;/a&gt;&lt;span class="na"&gt;sqluri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;pymysql://ffsync:&amp;lt;secret&amp;gt;@localhost/ffsync&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="configure-firefox"&gt;
&lt;h3&gt;Configure Firefox&lt;/h3&gt;
&lt;p&gt;Go to &lt;code class="docutils literal"&gt;about:config&lt;/code&gt; and set:&lt;/p&gt;
&lt;pre class="literal-block"&gt;identity.sync.tokenserver.uri = http://box:5000/token/1.0/sync/1.5&lt;/pre&gt;
&lt;p&gt;(the default is &lt;a class="reference external" href="https://token.services.mozilla.com/1.0/sync/1.5"&gt;https://token.services.mozilla.com/1.0/sync/1.5&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;You will still need a firefox account to use the sync feature.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="troubleshooting"&gt;
&lt;h3&gt;Troubleshooting&lt;/h3&gt;
&lt;p&gt;syncserver complains that &lt;code class="docutils literal"&gt;public_url&lt;/code&gt; does not match &lt;code class="docutils literal"&gt;application url&lt;/code&gt;.
Public URL is set to "&lt;a class="reference external" href="http://hostname:5000/"&gt;http://hostname:5000/&lt;/a&gt;", error says that app url is
"&lt;a class="reference external" href="http://hostname/"&gt;http://hostname/&lt;/a&gt;".
Change &lt;code class="docutils literal"&gt;public_url&lt;/code&gt; in config file to hostname w/o port,
get the same error but the other way round.
Set &lt;code class="docutils literal"&gt;force_wsgi_environ = true&lt;/code&gt; fixed this.&lt;/p&gt;
&lt;p&gt;With SQLite - errors when clients try to sync:&lt;/p&gt;
&lt;pre class="literal-block"&gt;(OperationalError) cannot start a transaction within a transaction&lt;/pre&gt;
&lt;p&gt;Tried to limit gunicorn to a single worker thread - no help.
Installed MariaDB to work around this.&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;Type &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;about:sync-log&lt;/span&gt;&lt;/code&gt; into Firefox address bar to see ...sync logs.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Some additional links:
- &lt;a class="reference external" href="http://blog.sysbite.org/run-a-firefox-sync-server-on-the-raspberry-pi/"&gt;http://blog.sysbite.org/run-a-firefox-sync-server-on-the-raspberry-pi/&lt;/a&gt;
- &lt;a class="reference external" href="http://www.raspberry-pi-geek.com/Archive/2015/11/The-new-Firefox-synchronizer"&gt;http://www.raspberry-pi-geek.com/Archive/2015/11/The-new-Firefox-synchronizer&lt;/a&gt;
- &lt;a class="reference external" href="https://wiki.archlinux.org/index.php/Mozilla_Firefox_Sync_Server"&gt;https://wiki.archlinux.org/index.php/Mozilla_Firefox_Sync_Server&lt;/a&gt;&lt;/p&gt;
&lt;hr class="docutils"&gt;
&lt;dl class="footnote brackets"&gt;
&lt;dt class="label" id="id1"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/firefox-sync-server-on-a-raspberry-pi/#id2"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://docs.services.mozilla.com/howtos/run-sync-1.5.html"&gt;https://docs.services.mozilla.com/howtos/run-sync-1.5.html&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id3"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/firefox-sync-server-on-a-raspberry-pi/#id4"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://tinycorelinux.net/8.x/armv6/releases/RPi/README"&gt;http://tinycorelinux.net/8.x/armv6/releases/RPi/README&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id5"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/firefox-sync-server-on-a-raspberry-pi/#id6"&gt;3&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://tinycorelinux.net/"&gt;http://tinycorelinux.net/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id7"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/firefox-sync-server-on-a-raspberry-pi/#id8"&gt;4&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://www.tldp.org/HOWTO/Flash-Memory-HOWTO/ext2.html"&gt;http://www.tldp.org/HOWTO/Flash-Memory-HOWTO/ext2.html&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id9"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/firefox-sync-server-on-a-raspberry-pi/#id10"&gt;5&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://virtualenv.pypa.io/en/stable/"&gt;https://virtualenv.pypa.io/en/stable/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id11"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/firefox-sync-server-on-a-raspberry-pi/#id12"&gt;6&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://trypyramid.com/"&gt;https://trypyramid.com/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id13"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/firefox-sync-server-on-a-raspberry-pi/#id14"&gt;7&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://gunicorn.org/"&gt;http://gunicorn.org/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id15"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/firefox-sync-server-on-a-raspberry-pi/#id16"&gt;8&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://virtualenv.pypa.io/en/stable/installation/"&gt;https://virtualenv.pypa.io/en/stable/installation/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id17"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/firefox-sync-server-on-a-raspberry-pi/#id18"&gt;9&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://github.com/mozilla-services/syncserver"&gt;https://github.com/mozilla-services/syncserver&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id19"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/firefox-sync-server-on-a-raspberry-pi/#id20"&gt;10&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://tinycorelinux.net/corebook.pdf"&gt;http://tinycorelinux.net/corebook.pdf&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id21"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/firefox-sync-server-on-a-raspberry-pi/#id22"&gt;11&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://wiki.tinycorelinux.net/wiki:mysql_persistence_guide"&gt;http://wiki.tinycorelinux.net/wiki:mysql_persistence_guide&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id23"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/firefox-sync-server-on-a-raspberry-pi/#id24"&gt;12&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://mariadb.com/kb/en/mariadb/starting-and-stopping-mariadb-automatically/"&gt;https://mariadb.com/kb/en/mariadb/starting-and-stopping-mariadb-automatically/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id25"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/firefox-sync-server-on-a-raspberry-pi/#id26"&gt;13&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://mariadb.com/kb/en/mariadb/configuring-mariadb-with-mycnf/"&gt;https://mariadb.com/kb/en/mariadb/configuring-mariadb-with-mycnf/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/div&gt;
&lt;/div&gt;</description><category>homeserver</category><category>linux</category><category>raspi</category><category>tinycore</category><guid>https://akeil.de/posts/firefox-sync-server-on-a-raspberry-pi/</guid><pubDate>Wed, 14 Sep 2016 15:50:27 GMT</pubDate></item><item><title>Coordinate Scripts With systemd</title><link>https://akeil.de/posts/coordinate-scripts-with-systemd/</link><dc:creator>Alexander Keil</dc:creator><description>&lt;div class="section" id="coordinate-scripts-with-systemd"&gt;
&lt;h2&gt;Coordinate Scripts With systemd&lt;/h2&gt;
&lt;dl class="field-list simple"&gt;
&lt;dt&gt;author&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;akeil&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;date&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;2013-12-14&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;version&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;1&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;Use systemd to execute scripts,
controlling the order of execution.&lt;/p&gt;
&lt;dl class="simple"&gt;
&lt;dt&gt;Assuming we have:&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;a single preparation unit - &lt;code class="docutils literal"&gt;prep.service&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;multiple "task" units - &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;task-N.service&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;a single cleanup script - &lt;code class="docutils literal"&gt;done.service&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;We want to run the &lt;em&gt;prep unit&lt;/em&gt; once,
then run all our &lt;em&gt;task units&lt;/em&gt;
and finally the &lt;em&gt;done unit&lt;/em&gt; once.&lt;/p&gt;
&lt;p&gt;Defining dependencies between tasks is relatively simple when each task
is represented by a &lt;a class="reference external" href="http://www.freedesktop.org/software/systemd/man/systemd.unit.html"&gt;systemd unit&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/coordinate-scripts-with-systemd/#id1" id="id2"&gt;1&lt;/a&gt;.
Dependencies are defined using &lt;code class="docutils literal"&gt;Requires=&lt;/code&gt; or &lt;code class="docutils literal"&gt;Wants=&lt;/code&gt;
and order of execution is defined with &lt;code class="docutils literal"&gt;After=&lt;/code&gt; and &lt;code class="docutils literal"&gt;Before=&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The only thing left is to make sure that the "preparation"
and "finish" steps are executed only once, even if multiple tasks are run
(and not once for each task).&lt;/p&gt;
&lt;p&gt;To achieve this a &lt;code class="docutils literal"&gt;.target&lt;/code&gt; is created
and all tasks are associated to that target.&lt;/p&gt;
&lt;!-- TEASER_END --&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;dl class="simple"&gt;
&lt;dt&gt;Steps:&lt;/dt&gt;
&lt;dd&gt;&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Create &lt;code class="docutils literal"&gt;.service&lt;/code&gt; files for the commands that must be executed &lt;em&gt;before&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create &lt;code class="docutils literal"&gt;.service&lt;/code&gt; for commands that should be executed &lt;em&gt;after&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create as single &lt;code class="docutils literal"&gt;.target&lt;/code&gt; for all tasks&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a &lt;code class="docutils literal"&gt;.service&lt;/code&gt; for each task, make it &lt;code class="docutils literal"&gt;WantedBy=&lt;/code&gt; the target.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/div&gt;
&lt;div class="section" id="target-unit"&gt;
&lt;h3&gt;Target Unit&lt;/h3&gt;
&lt;p&gt;A &lt;a class="reference external" href="http://www.freedesktop.org/software/systemd/man/systemd.target.html"&gt;systemd target&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/coordinate-scripts-with-systemd/#id3" id="id4"&gt;2&lt;/a&gt; is used to group several units together.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;target&lt;/em&gt; file goes into &lt;code class="docutils literal"&gt;/etc/systemd/system&lt;/code&gt; and looks like this:&lt;/p&gt;
&lt;pre class="code ini"&gt;&lt;a name="rest_code_e7c75c92097c41c0a61f5a55125bd5c8-1"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# tasks.target ---------------------------------------&lt;/span&gt;
&lt;a name="rest_code_e7c75c92097c41c0a61f5a55125bd5c8-2"&gt;&lt;/a&gt;&lt;span class="k"&gt;[Unit]&lt;/span&gt;
&lt;a name="rest_code_e7c75c92097c41c0a61f5a55125bd5c8-3"&gt;&lt;/a&gt;&lt;span class="na"&gt;Description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;Tasks Target&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;The target can be started manually with:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_91cdad81cfc045cd809226b8df2be069-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;#&lt;/span&gt; systemctl start tasks.target
&lt;/pre&gt;&lt;p&gt;The &lt;em&gt;task units&lt;/em&gt; are associated with the target using &lt;code class="docutils literal"&gt;WantedBy=&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This means, if we &lt;em&gt;enable&lt;/em&gt; one or more &lt;em&gt;task units&lt;/em&gt; with&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_50eb4ceb35f145f292203870b6683152-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;#&lt;/span&gt; systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; task-1.service task-2.service
&lt;/pre&gt;&lt;p&gt;... starting the &lt;code class="docutils literal"&gt;tasks.target&lt;/code&gt; will start all of the associated
units.&lt;/p&gt;
&lt;p&gt;This allows us to start all tasks with a single invocation
which in turn means that we execute any number of tasks
and the &lt;em&gt;prep&lt;/em&gt; and &lt;em&gt;done&lt;/em&gt; units only once.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="task-units"&gt;
&lt;h3&gt;Task Units&lt;/h3&gt;
&lt;p&gt;Each &lt;em&gt;task unit&lt;/em&gt; specifies the &lt;code class="docutils literal"&gt;prep.service&lt;/code&gt; as a
precondition using &lt;code class="docutils literal"&gt;Requires=&lt;/code&gt; and &lt;code class="docutils literal"&gt;After=&lt;/code&gt;.
It also specifies the &lt;code class="docutils literal"&gt;done.service&lt;/code&gt; using &lt;code class="docutils literal"&gt;Wants=&lt;/code&gt;
and &lt;code class="docutils literal"&gt;Before=&lt;/code&gt; to have it run after the tasks.&lt;/p&gt;
&lt;pre class="code ini"&gt;&lt;a name="rest_code_7a31e88d12b1475a9a15985e4752a076-1"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# task.service ---------------------------------------&lt;/span&gt;
&lt;a name="rest_code_7a31e88d12b1475a9a15985e4752a076-2"&gt;&lt;/a&gt;&lt;span class="k"&gt;[Unit]&lt;/span&gt;
&lt;a name="rest_code_7a31e88d12b1475a9a15985e4752a076-3"&gt;&lt;/a&gt;&lt;span class="na"&gt;Description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;A task unit&lt;/span&gt;
&lt;a name="rest_code_7a31e88d12b1475a9a15985e4752a076-4"&gt;&lt;/a&gt;&lt;span class="na"&gt;Requires&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;prep.service&lt;/span&gt;
&lt;a name="rest_code_7a31e88d12b1475a9a15985e4752a076-5"&gt;&lt;/a&gt;&lt;span class="na"&gt;After&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;prep.service&lt;/span&gt;
&lt;a name="rest_code_7a31e88d12b1475a9a15985e4752a076-6"&gt;&lt;/a&gt;&lt;span class="na"&gt;Wants&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;done.service&lt;/span&gt;
&lt;a name="rest_code_7a31e88d12b1475a9a15985e4752a076-7"&gt;&lt;/a&gt;&lt;span class="na"&gt;Before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;done.service&lt;/span&gt;
&lt;a name="rest_code_7a31e88d12b1475a9a15985e4752a076-8"&gt;&lt;/a&gt;
&lt;a name="rest_code_7a31e88d12b1475a9a15985e4752a076-9"&gt;&lt;/a&gt;&lt;span class="k"&gt;[Service]&lt;/span&gt;
&lt;a name="rest_code_7a31e88d12b1475a9a15985e4752a076-10"&gt;&lt;/a&gt;&lt;span class="na"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;someuser&lt;/span&gt;
&lt;a name="rest_code_7a31e88d12b1475a9a15985e4752a076-11"&gt;&lt;/a&gt;&lt;span class="na"&gt;Group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;somegroup&lt;/span&gt;
&lt;a name="rest_code_7a31e88d12b1475a9a15985e4752a076-12"&gt;&lt;/a&gt;&lt;span class="na"&gt;Type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;oneshot&lt;/span&gt;
&lt;a name="rest_code_7a31e88d12b1475a9a15985e4752a076-13"&gt;&lt;/a&gt;&lt;span class="na"&gt;ExecStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/usr/local/bin/script.sh&lt;/span&gt;
&lt;a name="rest_code_7a31e88d12b1475a9a15985e4752a076-14"&gt;&lt;/a&gt;
&lt;a name="rest_code_7a31e88d12b1475a9a15985e4752a076-15"&gt;&lt;/a&gt;&lt;span class="k"&gt;[Install]&lt;/span&gt;
&lt;a name="rest_code_7a31e88d12b1475a9a15985e4752a076-16"&gt;&lt;/a&gt;&lt;span class="na"&gt;WantedBy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;tasks.target&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;The &lt;code class="docutils literal"&gt;Requires=prep.service&lt;/code&gt; directive means that the task is not started
if the &lt;code class="docutils literal"&gt;prep.service&lt;/code&gt; failed.
Using &lt;code class="docutils literal"&gt;Wants=prep.service&lt;/code&gt; would try to start the &lt;em&gt;prep unit&lt;/em&gt; first
but continue with the tasks regardless of whether preparation was
successful or not.&lt;/p&gt;
&lt;p&gt;The &lt;code class="docutils literal"&gt;User=&lt;/code&gt; and &lt;code class="docutils literal"&gt;Group=&lt;/code&gt; properties can be used to run the command
under a different user than root.&lt;/p&gt;
&lt;p&gt;Not all tasks have to specify the same dependencies.
It is also possible that tasks depend on each other.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="prep-and-done-units"&gt;
&lt;h3&gt;Prep and Done Units&lt;/h3&gt;
&lt;p&gt;The &lt;code class="docutils literal"&gt;prep.service&lt;/code&gt; and the &lt;code class="docutils literal"&gt;done.service&lt;/code&gt; are plain units
and do not define any dependencies.&lt;/p&gt;
&lt;p&gt;the &lt;em&gt;prep unit&lt;/em&gt; should be configured with &lt;code class="docutils literal"&gt;Type=oneshot&lt;/code&gt;
to have it complete before the first task can be started.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="service-type"&gt;
&lt;h3&gt;Service Type&lt;/h3&gt;
&lt;p&gt;By default, &lt;code class="docutils literal"&gt;Before=&lt;/code&gt; and &lt;code class="docutils literal"&gt;After=&lt;/code&gt; only control the order in which
services are &lt;em&gt;started&lt;/em&gt;.
To make sure that the &lt;em&gt;prep unit&lt;/em&gt; completes before the tasks are started,
use &lt;code class="docutils literal"&gt;Type=oneshot&lt;/code&gt; in the &lt;code class="docutils literal"&gt;[Service]&lt;/code&gt; definition.&lt;/p&gt;
&lt;p&gt;From the &lt;a class="reference external" href="http://www.freedesktop.org/software/systemd/man/systemd.service.html"&gt;systemd service&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/coordinate-scripts-with-systemd/#id5" id="id6"&gt;3&lt;/a&gt; documentation:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Behavior of oneshot is similar to simple;
however, it is expected that the process has to exit
before systemd starts follow-up units.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The same goes for the &lt;em&gt;task units&lt;/em&gt; if the &lt;em&gt;done unit&lt;/em&gt; should only
be executed after the tasks are complete.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="locations"&gt;
&lt;h3&gt;Locations&lt;/h3&gt;
&lt;dl class="simple"&gt;
&lt;dt&gt;Custom unit files go into:&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal"&gt;/etc/systemd/system&lt;/code&gt; for system-wide scope&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;~/.config/systemd/user&lt;/span&gt;&lt;/code&gt; for user-specific scope&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;hr class="docutils"&gt;
&lt;dl class="footnote brackets"&gt;
&lt;dt class="label" id="id1"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/coordinate-scripts-with-systemd/#id2"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://www.freedesktop.org/software/systemd/man/systemd.unit.html"&gt;http://www.freedesktop.org/software/systemd/man/systemd.unit.html&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id3"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/coordinate-scripts-with-systemd/#id4"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://www.freedesktop.org/software/systemd/man/systemd.target.html"&gt;http://www.freedesktop.org/software/systemd/man/systemd.target.html&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id5"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/coordinate-scripts-with-systemd/#id6"&gt;3&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://www.freedesktop.org/software/systemd/man/systemd.service.html"&gt;http://www.freedesktop.org/software/systemd/man/systemd.service.html&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/div&gt;
&lt;/div&gt;</description><category>linux</category><category>systemd</category><guid>https://akeil.de/posts/coordinate-scripts-with-systemd/</guid><pubDate>Sun, 14 Dec 2014 13:43:38 GMT</pubDate></item><item><title>Syncthing on Arch Linux</title><link>https://akeil.de/posts/syncthing-on-arch-linux/</link><dc:creator>Alexander Keil</dc:creator><description>&lt;div class="section" id="syncthing-on-arch-linux"&gt;
&lt;h2&gt;Syncthing on Arch Linux&lt;/h2&gt;
&lt;dl class="field-list simple"&gt;
&lt;dt&gt;author&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;akeil&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;date&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;2014-12-10&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;version&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;1&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;&lt;a class="reference external" href="http://syncthing.net/"&gt;Syncthing&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/syncthing-on-arch-linux/#id1" id="id2"&gt;1&lt;/a&gt; is an open source tool which keeps files
on different machines in sync.&lt;/p&gt;
&lt;!-- TEASER_END --&gt;
&lt;div class="section" id="installation"&gt;
&lt;h3&gt;Installation&lt;/h3&gt;
&lt;p&gt;For Arch Linux Syncthing is available in the &lt;a class="reference external" href="https://www.archlinux.org/packages/community/x86_64/syncthing/"&gt;community repository&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/syncthing-on-arch-linux/#id5" id="id6"&gt;3&lt;/a&gt;
and can be installed easily:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_c585698c890040e8a81f3d8b4a80a6e7-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;#&lt;/span&gt; pacman -S syncthing
&lt;/pre&gt;&lt;p&gt;The Syncthing package installs a &lt;em&gt;.service&lt;/em&gt; file at
&lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;/usr/lib/systemd/system/syncthing@.service&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;pre class="code ini"&gt;&lt;a name="rest_code_ed53a52e93b745cba24ce30ee89c3b2b-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;[Unit]&lt;/span&gt;
&lt;a name="rest_code_ed53a52e93b745cba24ce30ee89c3b2b-2"&gt;&lt;/a&gt;&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Syncthing service for %i&lt;/span&gt;
&lt;a name="rest_code_ed53a52e93b745cba24ce30ee89c3b2b-3"&gt;&lt;/a&gt;&lt;span class="na"&gt;After&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network.target&lt;/span&gt;
&lt;a name="rest_code_ed53a52e93b745cba24ce30ee89c3b2b-4"&gt;&lt;/a&gt;
&lt;a name="rest_code_ed53a52e93b745cba24ce30ee89c3b2b-5"&gt;&lt;/a&gt;&lt;span class="k"&gt;[Service]&lt;/span&gt;
&lt;a name="rest_code_ed53a52e93b745cba24ce30ee89c3b2b-6"&gt;&lt;/a&gt;&lt;span class="na"&gt;User&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;%i&lt;/span&gt;
&lt;a name="rest_code_ed53a52e93b745cba24ce30ee89c3b2b-7"&gt;&lt;/a&gt;&lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;STNORESTART=yes&lt;/span&gt;
&lt;a name="rest_code_ed53a52e93b745cba24ce30ee89c3b2b-8"&gt;&lt;/a&gt;&lt;span class="na"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/syncthing&lt;/span&gt;
&lt;a name="rest_code_ed53a52e93b745cba24ce30ee89c3b2b-9"&gt;&lt;/a&gt;&lt;span class="na"&gt;Restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;always&lt;/span&gt;
&lt;a name="rest_code_ed53a52e93b745cba24ce30ee89c3b2b-10"&gt;&lt;/a&gt;
&lt;a name="rest_code_ed53a52e93b745cba24ce30ee89c3b2b-11"&gt;&lt;/a&gt;&lt;span class="k"&gt;[Install]&lt;/span&gt;
&lt;a name="rest_code_ed53a52e93b745cba24ce30ee89c3b2b-12"&gt;&lt;/a&gt;&lt;span class="na"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;multi-user.target&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;When enabling or starting the service,
supply the user under which syncthing will run:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_6a0eea4bd3314fc9a616fa6f7e7dd5d3-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;#&lt;/span&gt; systemctl start syncthing@yourname
&lt;a name="rest_code_6a0eea4bd3314fc9a616fa6f7e7dd5d3-2"&gt;&lt;/a&gt;&lt;span class="gp"&gt;#&lt;/span&gt; systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; syncthing@yourname
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="section" id="configuration"&gt;
&lt;h3&gt;Configuration&lt;/h3&gt;
&lt;p&gt;Configuration is located at &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;~/.config/syncthing/&lt;/span&gt;&lt;/code&gt;.
The directory and config files inside it will be created
when Syncthing is first started.
It is described in detail in the &lt;a class="reference external" href="https://discourse.syncthing.net/c/documentation"&gt;Syncthing documentation&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/syncthing-on-arch-linux/#id3" id="id4"&gt;2&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code class="docutils literal"&gt;config.xml&lt;/code&gt; can either be edited via the web GUI
or manually.
By default, the Web GUI is only reachable from the same machine.&lt;/p&gt;
&lt;p&gt;If Syncthing is installed on a machine without a desktop, one can either
edit the configuration manually for &lt;em&gt;all&lt;/em&gt; tasks or at least to make
the GUI available on the network.
Manual configuration will also be necessary if you want to change the
port number under which the GUI is available, for example when
the default port &lt;strong&gt;8080&lt;/strong&gt; is already in use.&lt;/p&gt;
&lt;pre class="code xml"&gt;&lt;a name="rest_code_65f8dff76bce410bab2fc9a094813f06-1"&gt;&lt;/a&gt;&lt;span class="nt"&gt;&amp;lt;configuration&lt;/span&gt; &lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;"6"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;a name="rest_code_65f8dff76bce410bab2fc9a094813f06-2"&gt;&lt;/a&gt;    ...
&lt;a name="rest_code_65f8dff76bce410bab2fc9a094813f06-3"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;&amp;lt;gui&lt;/span&gt; &lt;span class="na"&gt;enabled=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;tls=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;a name="rest_code_65f8dff76bce410bab2fc9a094813f06-4"&gt;&lt;/a&gt;        &lt;span class="c"&gt;&amp;lt;!-- default: 127.0.0.1:8080 --&amp;gt;&lt;/span&gt;
&lt;a name="rest_code_65f8dff76bce410bab2fc9a094813f06-5"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;&amp;lt;address&amp;gt;&lt;/span&gt;0.0.0.0:8888&lt;span class="nt"&gt;&amp;lt;/address&amp;gt;&lt;/span&gt;
&lt;a name="rest_code_65f8dff76bce410bab2fc9a094813f06-6"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;&amp;lt;/gui&amp;gt;&lt;/span&gt;
&lt;a name="rest_code_65f8dff76bce410bab2fc9a094813f06-7"&gt;&lt;/a&gt;    ...
&lt;a name="rest_code_65f8dff76bce410bab2fc9a094813f06-8"&gt;&lt;/a&gt;&lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;The first thing you might want to do is to remove the &lt;code class="docutils literal"&gt;~/Sync/&lt;/code&gt; directory
that was created on startup and replace it with something else.&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_e3f568cbcc9748fca998ee6aeb90f2e2-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;#&lt;/span&gt; systemctl stop syncthing@yourname
&lt;a name="rest_code_e3f568cbcc9748fca998ee6aeb90f2e2-2"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; rm -r ~/Sync
&lt;/pre&gt;&lt;p&gt;Edit the config file to include the directories to sync:&lt;/p&gt;
&lt;pre class="code xml"&gt;&lt;a name="rest_code_7c699ff2a69d4aac951719e7adf65be2-1"&gt;&lt;/a&gt;&lt;span class="nt"&gt;&amp;lt;configuration&lt;/span&gt; &lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;"6"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;a name="rest_code_7c699ff2a69d4aac951719e7adf65be2-2"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;&amp;lt;folder&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"default"&lt;/span&gt; &lt;span class="na"&gt;path=&lt;/span&gt;&lt;span class="s"&gt;"/home/yourname/sync-folder"&lt;/span&gt; &lt;span class="na"&gt;ro=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt; &lt;span class="na"&gt;rescanIntervalS=&lt;/span&gt;&lt;span class="s"&gt;"60"&lt;/span&gt; &lt;span class="na"&gt;ignorePerms=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;a name="rest_code_7c699ff2a69d4aac951719e7adf65be2-3"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;&amp;lt;device&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"[ID-1]"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/device&amp;gt;&lt;/span&gt;
&lt;a name="rest_code_7c699ff2a69d4aac951719e7adf65be2-4"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;&amp;lt;versioning&amp;gt;&amp;lt;/versioning&amp;gt;&lt;/span&gt;
&lt;a name="rest_code_7c699ff2a69d4aac951719e7adf65be2-5"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;&amp;lt;lenientMtimes&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/lenientMtimes&amp;gt;&lt;/span&gt;
&lt;a name="rest_code_7c699ff2a69d4aac951719e7adf65be2-6"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;&amp;lt;copiers&amp;gt;&lt;/span&gt;0&lt;span class="nt"&gt;&amp;lt;/copiers&amp;gt;&lt;/span&gt;
&lt;a name="rest_code_7c699ff2a69d4aac951719e7adf65be2-7"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;&amp;lt;pullers&amp;gt;&lt;/span&gt;0&lt;span class="nt"&gt;&amp;lt;/pullers&amp;gt;&lt;/span&gt;
&lt;a name="rest_code_7c699ff2a69d4aac951719e7adf65be2-8"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;&amp;lt;finishers&amp;gt;&lt;/span&gt;0&lt;span class="nt"&gt;&amp;lt;/finishers&amp;gt;&lt;/span&gt;
&lt;a name="rest_code_7c699ff2a69d4aac951719e7adf65be2-9"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;&amp;lt;/folder&amp;gt;&lt;/span&gt;
&lt;a name="rest_code_7c699ff2a69d4aac951719e7adf65be2-10"&gt;&lt;/a&gt;    ...
&lt;a name="rest_code_7c699ff2a69d4aac951719e7adf65be2-11"&gt;&lt;/a&gt;&lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Create more &lt;code class="docutils literal"&gt;&amp;lt;folder&amp;gt;&lt;/code&gt; elements if you want to sync other directories.&lt;/p&gt;
&lt;p&gt;Changes will take effect when Syncthing is restarted:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_73a3a50c7f8c4e68a9a2468354e2bcb5-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;#&lt;/span&gt; systemctl restart syncthing@yourname
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="section" id="start-syncing"&gt;
&lt;h3&gt;Start Syncing&lt;/h3&gt;
&lt;p&gt;Repeat installation and configuration on other machines
you which to keep in sync.
On each machine, Syncthing must be started at least once to determine the
&lt;em&gt;device ID&lt;/em&gt; for that machine.&lt;/p&gt;
&lt;p&gt;Edit the config file to include the new devices
and add the device to each folder you want to sync:&lt;/p&gt;
&lt;pre class="code xml"&gt;&lt;a name="rest_code_41e3d547230240b680f6cb1ee1f9f12a-1"&gt;&lt;/a&gt;&lt;span class="nt"&gt;&amp;lt;configuration&lt;/span&gt; &lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;"6"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;a name="rest_code_41e3d547230240b680f6cb1ee1f9f12a-2"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;&amp;lt;folder&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"notes"&lt;/span&gt; &lt;span class="na"&gt;path=&lt;/span&gt;&lt;span class="s"&gt;"/home/akeil/notes"&lt;/span&gt; &lt;span class="na"&gt;ro=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt; &lt;span class="na"&gt;rescanIntervalS=&lt;/span&gt;&lt;span class="s"&gt;"60"&lt;/span&gt; &lt;span class="na"&gt;ignorePerms=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;a name="rest_code_41e3d547230240b680f6cb1ee1f9f12a-3"&gt;&lt;/a&gt;        &lt;span class="c"&gt;&amp;lt;!-- the ID of the local device --&amp;gt;&lt;/span&gt;
&lt;a name="rest_code_41e3d547230240b680f6cb1ee1f9f12a-4"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;&amp;lt;device&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"[ID-1]"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/device&amp;gt;&lt;/span&gt;
&lt;a name="rest_code_41e3d547230240b680f6cb1ee1f9f12a-5"&gt;&lt;/a&gt;        &lt;span class="c"&gt;&amp;lt;!-- one or more IDs of remote devices --&amp;gt;&lt;/span&gt;
&lt;a name="rest_code_41e3d547230240b680f6cb1ee1f9f12a-6"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;&amp;lt;device&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"[ID-2]"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/device&amp;gt;&lt;/span&gt;
&lt;a name="rest_code_41e3d547230240b680f6cb1ee1f9f12a-7"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;&amp;lt;versioning&amp;gt;&amp;lt;/versioning&amp;gt;&lt;/span&gt;
&lt;a name="rest_code_41e3d547230240b680f6cb1ee1f9f12a-8"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;&amp;lt;lenientMtimes&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/lenientMtimes&amp;gt;&lt;/span&gt;
&lt;a name="rest_code_41e3d547230240b680f6cb1ee1f9f12a-9"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;&amp;lt;copiers&amp;gt;&lt;/span&gt;1&lt;span class="nt"&gt;&amp;lt;/copiers&amp;gt;&lt;/span&gt;
&lt;a name="rest_code_41e3d547230240b680f6cb1ee1f9f12a-10"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;&amp;lt;pullers&amp;gt;&lt;/span&gt;16&lt;span class="nt"&gt;&amp;lt;/pullers&amp;gt;&lt;/span&gt;
&lt;a name="rest_code_41e3d547230240b680f6cb1ee1f9f12a-11"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;&amp;lt;finishers&amp;gt;&lt;/span&gt;1&lt;span class="nt"&gt;&amp;lt;/finishers&amp;gt;&lt;/span&gt;
&lt;a name="rest_code_41e3d547230240b680f6cb1ee1f9f12a-12"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;&amp;lt;/folder&amp;gt;&lt;/span&gt;
&lt;a name="rest_code_41e3d547230240b680f6cb1ee1f9f12a-13"&gt;&lt;/a&gt;    &lt;span class="c"&gt;&amp;lt;!-- the local device --&amp;gt;&lt;/span&gt;
&lt;a name="rest_code_41e3d547230240b680f6cb1ee1f9f12a-14"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;&amp;lt;device&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"[ID-1]"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"other-machine"&lt;/span&gt; &lt;span class="na"&gt;compression=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;introducer=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;a name="rest_code_41e3d547230240b680f6cb1ee1f9f12a-15"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;&amp;lt;address&amp;gt;&lt;/span&gt;dynamic&lt;span class="nt"&gt;&amp;lt;/address&amp;gt;&lt;/span&gt;
&lt;a name="rest_code_41e3d547230240b680f6cb1ee1f9f12a-16"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;&amp;lt;/device&amp;gt;&lt;/span&gt;
&lt;a name="rest_code_41e3d547230240b680f6cb1ee1f9f12a-17"&gt;&lt;/a&gt;    &lt;span class="c"&gt;&amp;lt;!-- remote devices to sync with --&amp;gt;&lt;/span&gt;
&lt;a name="rest_code_41e3d547230240b680f6cb1ee1f9f12a-18"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;&amp;lt;device&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"[ID-1] name="&lt;/span&gt;&lt;span class="err"&gt;home"&lt;/span&gt; &lt;span class="na"&gt;compression=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;introducer=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;a name="rest_code_41e3d547230240b680f6cb1ee1f9f12a-19"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;&amp;lt;address&amp;gt;&lt;/span&gt;dynamic&lt;span class="nt"&gt;&amp;lt;/address&amp;gt;&lt;/span&gt;
&lt;a name="rest_code_41e3d547230240b680f6cb1ee1f9f12a-20"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;&amp;lt;/device&amp;gt;&lt;/span&gt;
&lt;a name="rest_code_41e3d547230240b680f6cb1ee1f9f12a-21"&gt;&lt;/a&gt;    ...
&lt;a name="rest_code_41e3d547230240b680f6cb1ee1f9f12a-22"&gt;&lt;/a&gt;&lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Restart syncthing:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_3772e375c48e442fbedceb3921952422-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;#&lt;/span&gt; systemctl restart syncthing@yourname
&lt;/pre&gt;&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;You must create &lt;code class="docutils literal"&gt;&amp;lt;device&amp;gt;&lt;/code&gt; elements on both machines.
(inside the &lt;code class="docutils literal"&gt;&amp;lt;folder&amp;gt;&lt;/code&gt;'s you wish to sync and in the general section
of known devices.).&lt;/p&gt;
&lt;p&gt;If not, entries like this appear in the log and nothing is synced:&lt;/p&gt;
&lt;pre class="literal-block"&gt;[...] Connection from 192.168.1.100:12345 with unknown device ID [ID-1];&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="logfiles-journal"&gt;
&lt;h4&gt;Logfiles (Journal)&lt;/h4&gt;
&lt;p&gt;View logs with:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_3aa94d0ee5d5433bbfa468e8b6ec1c94-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; journalctl --unit syncthing@yourname
&lt;/pre&gt;&lt;p&gt;Or&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_8d13ff8eb0ea45988aa4dfd1c473176d-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; systemctl status syncthing@yourname
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="misc"&gt;
&lt;h3&gt;Misc&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;There is a &lt;a class="reference external" href="https://f-droid.org/repository/browse/?fdid=com.nutomic.syncthingandroid"&gt;Syncthing Android App&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/syncthing-on-arch-linux/#id7" id="id8"&gt;4&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;And &lt;a class="reference external" href="http://jasonwryan.com/blog/2014/05/10/syncthing/"&gt;another blog post&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/syncthing-on-arch-linux/#id9" id="id10"&gt;5&lt;/a&gt; on Syncthing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;There is &lt;a class="reference external" href="http://archlinuxarm.org/packages?arch=&amp;amp;search=syncthing"&gt;Syncthing for Arch Linux ARM&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/syncthing-on-arch-linux/#id11" id="id12"&gt;6&lt;/a&gt; which means it can run on a &lt;a class="reference external" href="http://www.raspberrypi.org/"&gt;RaspberryPi&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/syncthing-on-arch-linux/#id13" id="id14"&gt;7&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr class="docutils"&gt;
&lt;dl class="footnote brackets"&gt;
&lt;dt class="label" id="id1"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/syncthing-on-arch-linux/#id2"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://syncthing.net/"&gt;http://syncthing.net/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id3"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/syncthing-on-arch-linux/#id4"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://discourse.syncthing.net/c/documentation"&gt;https://discourse.syncthing.net/c/documentation&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id5"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/syncthing-on-arch-linux/#id6"&gt;3&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://www.archlinux.org/packages/community/x86_64/syncthing/"&gt;https://www.archlinux.org/packages/community/x86_64/syncthing/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id7"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/syncthing-on-arch-linux/#id8"&gt;4&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://f-droid.org/repository/browse/?fdid=com.nutomic.syncthingandroid"&gt;https://f-droid.org/repository/browse/?fdid=com.nutomic.syncthingandroid&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id9"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/syncthing-on-arch-linux/#id10"&gt;5&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://jasonwryan.com/blog/2014/05/10/syncthing/"&gt;http://jasonwryan.com/blog/2014/05/10/syncthing/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id11"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/syncthing-on-arch-linux/#id12"&gt;6&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://archlinuxarm.org/packages?arch=&amp;amp;search=syncthing"&gt;http://archlinuxarm.org/packages?arch=&amp;amp;search=syncthing&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id13"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/syncthing-on-arch-linux/#id14"&gt;7&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://www.raspberrypi.org/"&gt;http://www.raspberrypi.org/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/div&gt;
&lt;/div&gt;</description><category>linux</category><category>raspi</category><guid>https://akeil.de/posts/syncthing-on-arch-linux/</guid><pubDate>Wed, 10 Dec 2014 20:40:00 GMT</pubDate></item><item><title>Spellcheck with aspell</title><link>https://akeil.de/posts/spellcheck-with-aspell/</link><dc:creator>Alexander Keil</dc:creator><description>&lt;div class="section" id="spellcheck-with-aspell"&gt;
&lt;h2&gt;Spellcheck with aspell&lt;/h2&gt;
&lt;dl class="field-list simple"&gt;
&lt;dt&gt;author&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;akeil&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;date&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;2014-05-10&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;version&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;1&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;&lt;a class="reference external" href="http://aspell.net/"&gt;aspell&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/spellcheck-with-aspell/#id1" id="id2"&gt;1&lt;/a&gt; is an open-source spell checker
which can be used from the command line.
Aspell can be used when working with text-based files such as ReStructured Text or
markdown (or plain text, of course).&lt;/p&gt;
&lt;p&gt;aspell has filters to spellcheck E-Mails or HTML documents.&lt;/p&gt;
&lt;p&gt;There are also &lt;a class="reference external" href="https://github.com/WojciechMula/aspell-python"&gt;Python bindings for aspell&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/spellcheck-with-aspell/#id3" id="id4"&gt;2&lt;/a&gt;.&lt;/p&gt;
&lt;!-- TEASER_END --&gt;
&lt;p&gt;Invoke aspell like this to open an interactive terminal:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_3fcf4d5033bd4f83983c52af040f9419-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; aspell check document.txt
&lt;/pre&gt;&lt;p&gt;By default, aspell uses its built-in dictionary
and an additional user dictionary located at &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;~/.aspell.en.pws&lt;/span&gt;&lt;/code&gt;
(for an English localization).
The user dictionary holds any words that were selected as "add to dictionary"
during an aspell session and it can be edited manually.&lt;/p&gt;
&lt;p&gt;If the file to be checked contains specific terms which should
not be treated as misspellings
but should also not be included in the global or user based dictionary,
one can define an additional dictionary on the command line.
This is useful if the document contains for example technical
or domain-specific terms, abbreviations, etc.&lt;/p&gt;
&lt;p&gt;To check using an additional dictionary:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_adce6050a8324fceb3b941da4853e5bd-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; aspell --add-extra-dicts&lt;span class="o"&gt;=&lt;/span&gt;project-dict.pws check document.txt
&lt;/pre&gt;&lt;p&gt;This can be useful if the file to be checked contains specific
terms that should not be treated as misspellings but should also
not be included in the global dictionary
(like technical or domain-specific terms, abbreviations, ...).&lt;/p&gt;
&lt;p&gt;If the file is modified during the aspell session, a backup with the original
content is created. To run without backing up the uncorrected original:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_6dcdd5971beb427dbdb20bcd7ac63ae0-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; aspell -x check document.txt
&lt;/pre&gt;&lt;p&gt;To list misspelled words from a file, use &lt;code class="docutils literal"&gt;list&lt;/code&gt;.&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_9e5f6bf5614449518d7b5edf666e2c4d-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; aspell list &amp;lt; document.txt
&lt;/pre&gt;&lt;p&gt;Aspell does not accept shell-wildcards in arguments, so it is not possible
to &lt;code class="docutils literal"&gt;aspell check &lt;span class="pre"&gt;directory/*.txt&lt;/span&gt;&lt;/code&gt;.
To spellcheck multiple files, use &lt;code class="docutils literal"&gt;find&lt;/code&gt; (from &lt;a class="reference external" href="https://paulbradley.org/aspell/"&gt;paulbradley&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/spellcheck-with-aspell/#id5" id="id6"&gt;3&lt;/a&gt;):&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_d8e344eeacfd4b7597033a7257fdb106-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; find *.txt -exec aspell check &lt;span class="o"&gt;{}&lt;/span&gt; &lt;span class="se"&gt;\;&lt;/span&gt;
&lt;/pre&gt;&lt;div class="topic"&gt;
&lt;p class="topic-title"&gt;Interesting Command Line Options&lt;/p&gt;
&lt;dl class="simple"&gt;
&lt;dt&gt;-c FILE, check FILE&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;specify a file to be checked&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;list&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;Generate a list of misspelled words.
will not correct anything.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;clean&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;Clean up a wordlist and output a new wordlist
where every line is a valid word.&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;dl class="option-list"&gt;
&lt;dt&gt;&lt;kbd&gt;&lt;span class="option"&gt;--add-extra-dicts &lt;var&gt;FILE&lt;/var&gt;&lt;/span&gt;&lt;/kbd&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;Use &lt;code class="docutils literal"&gt;FILE&lt;/code&gt; as an additional wordlist.
The &lt;em&gt;absolute&lt;/em&gt; path must be specified
and the path may &lt;em&gt;not&lt;/em&gt; contain shell variables.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;&lt;kbd&gt;&lt;span class="option"&gt;-b&lt;/span&gt;, &lt;span class="option"&gt;--backup&lt;/span&gt;&lt;/kbd&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;Create a backup-file (&lt;code class="docutils literal"&gt;.bak&lt;/code&gt;) with the uncorrected file.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;&lt;kbd&gt;&lt;span class="option"&gt;-x&lt;/span&gt;, &lt;span class="option"&gt;--dont-backup&lt;/span&gt;&lt;/kbd&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;Do not create a backup (&lt;code class="docutils literal"&gt;.bak&lt;/code&gt;) file.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;&lt;kbd&gt;&lt;span class="option"&gt;-l &lt;var&gt;LANGUAGE&lt;/var&gt;&lt;/span&gt;, &lt;span class="option"&gt;--lang &lt;var&gt;LANGUAGE&lt;/var&gt;&lt;/span&gt;&lt;/kbd&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;Specify the language to use
with the two-letter language code (e.g. "en")
optionally followed by the country code (e.g. "en-US").&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/div&gt;
&lt;div class="section" id="install"&gt;
&lt;h3&gt;Install&lt;/h3&gt;
&lt;p&gt;Install with:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_9281c8fb56a9414e92c3101023ec4c29-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;#&lt;/span&gt; apt-get install aspell
&lt;/pre&gt;&lt;p&gt;on a Debian based system.
Or&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_4f58a63cc2214148b73443a508d3de03-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;#&lt;/span&gt; pacman -S aspell
&lt;/pre&gt;&lt;p&gt;on ArchLinux.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="configure"&gt;
&lt;h3&gt;Configure&lt;/h3&gt;
&lt;p&gt;Relevant configuration files for aspell are:&lt;/p&gt;
&lt;dl class="simple"&gt;
&lt;dt&gt;&lt;code class="docutils literal"&gt;/etc/aspell.conf&lt;/code&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;The System-wide configuration.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;&lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;~/.aspell.conf&lt;/span&gt;&lt;/code&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;The per-user configuration file.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;&lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;~/.aspell.en.pws&lt;/span&gt;&lt;/code&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;The user-specific wordlist (for the &lt;em&gt;en&lt;/em&gt; locale).&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;Use additional &lt;a class="reference internal" href="https://akeil.de/posts/spellcheck-with-aspell/#wordlists"&gt;wordlists&lt;/a&gt; by specifying a path in the config file:&lt;/p&gt;
&lt;pre class="literal-block"&gt;# ~/.aspell.conf
# -------------------------------------------------------
add-extra-dicts /home/USERNAME/.aspell-tech-terms.en.pws
add-extra-dicts /home/USERNAME/.aspell-names.en.pws&lt;/pre&gt;
&lt;p&gt;Use multiple lines of &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;add-extra-dicts&lt;/span&gt;&lt;/code&gt; to add several wordlists.
The wordlists will be used in every invocation of aspell.&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;The paths must be absolute.
Shell variables/expansions like &lt;code class="docutils literal"&gt;~&lt;/code&gt;, &lt;code class="docutils literal"&gt;/path/*&lt;/code&gt; or &lt;code class="docutils literal"&gt;$HOME&lt;/code&gt;
will &lt;em&gt;not&lt;/em&gt; work.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="wordlists"&gt;
&lt;h4&gt;Wordlists&lt;/h4&gt;
&lt;p&gt;aspell &lt;em&gt;wordlists&lt;/em&gt; are simple text files with one word per line.&lt;/p&gt;
&lt;p&gt;The first line must be:&lt;/p&gt;
&lt;pre class="literal-block"&gt;personal_ws-1.1 en 10&lt;/pre&gt;
&lt;p&gt;Where &lt;strong&gt;en&lt;/strong&gt; is your &lt;strong&gt;locale&lt;/strong&gt;
and &lt;strong&gt;10&lt;/strong&gt; would be the &lt;strong&gt;number of words&lt;/strong&gt; in the wordlist.
The number of words does not have to be exact but &lt;em&gt;should&lt;/em&gt; hint the
actual number of words in the file.&lt;/p&gt;
&lt;p&gt;The conventional file extension is &lt;code class="docutils literal"&gt;.pws&lt;/code&gt; but any filename can be used.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="scripts"&gt;
&lt;h3&gt;Scripts&lt;/h3&gt;
&lt;div class="section" id="a-spellcheck-command"&gt;
&lt;h4&gt;A Spellcheck Command&lt;/h4&gt;
&lt;p&gt;A small shell script to invoke aspell
and use an additional per-file wordlist
and/or a project specific wordlist.&lt;/p&gt;
&lt;p&gt;The script will look for a wordlist with the same name as the
file to be checked
and it will look for a wordlist in the current directory.
This allows to use a list of allowed words without adding them
to any of the globally used wordlists.&lt;/p&gt;
&lt;p&gt;Usage:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_19adc61f494e4cc49007fc6345a1464e-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; spellcheck document.txt
&lt;/pre&gt;&lt;p&gt;Code&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://akeil.de/listings/spellcheck.sh.html"&gt;spellcheck.sh&lt;/a&gt;  &lt;a class="reference external" href="https://akeil.de/listings/spellcheck.sh"&gt;(Source)&lt;/a&gt;&lt;/p&gt;
&lt;pre class="code bash"&gt;&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-1"&gt;&lt;/a&gt;&lt;span class="ch"&gt;#!/bin/bash&lt;/span&gt;
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-2"&gt;&lt;/a&gt;
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-3"&gt;&lt;/a&gt;&lt;span class="nb"&gt;set&lt;/span&gt; -o nounset
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-4"&gt;&lt;/a&gt;&lt;span class="nb"&gt;set&lt;/span&gt; -o errexit
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-5"&gt;&lt;/a&gt;
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-6"&gt;&lt;/a&gt;
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-7"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# Commands -----------------------------------------------&lt;/span&gt;
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-8"&gt;&lt;/a&gt;&lt;span class="nv"&gt;ASPELL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/bin/aspell
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-9"&gt;&lt;/a&gt;&lt;span class="nv"&gt;BASENAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/bin/basename
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-10"&gt;&lt;/a&gt;&lt;span class="nv"&gt;DIRNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/bin/dirname
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-11"&gt;&lt;/a&gt;&lt;span class="nv"&gt;PWD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/bin/pwd
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-12"&gt;&lt;/a&gt;
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-13"&gt;&lt;/a&gt;
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-14"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# Script -------------------------------------------------&lt;/span&gt;
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-15"&gt;&lt;/a&gt;&lt;span class="nv"&gt;extra&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-16"&gt;&lt;/a&gt;&lt;span class="nv"&gt;switch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"--add-extra-dicts"&lt;/span&gt;
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-17"&gt;&lt;/a&gt;
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-18"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# file specific dictionary&lt;/span&gt;
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-19"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# if there is a file with the same name&lt;/span&gt;
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-20"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# but ending in `.pws`&lt;/span&gt;
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-21"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# use it as an extra dict&lt;/span&gt;
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-22"&gt;&lt;/a&gt;&lt;span class="nv"&gt;directory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nv"&gt;$DIRNAME&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-23"&gt;&lt;/a&gt;&lt;span class="nv"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nv"&gt;$BASENAME&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-24"&gt;&lt;/a&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;%.*&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-25"&gt;&lt;/a&gt;&lt;span class="nv"&gt;dictpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$directory&lt;/span&gt;/&lt;span class="nv"&gt;$name&lt;/span&gt;.pws
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-26"&gt;&lt;/a&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; -f &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$dictpath&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-27"&gt;&lt;/a&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-28"&gt;&lt;/a&gt;    &lt;span class="nv"&gt;extra&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"--add-extra-dicts=`&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;&lt;span class="s2"&gt;`/&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;dictpath&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-29"&gt;&lt;/a&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-30"&gt;&lt;/a&gt;
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-31"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# extra dicts from the current directory&lt;/span&gt;
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-32"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# all files in the working directory&lt;/span&gt;
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-33"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# ending in `.pws` are used as extra dicts&lt;/span&gt;
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-34"&gt;&lt;/a&gt;
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-35"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# return nothing if *.pws does not match anything&lt;/span&gt;
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-36"&gt;&lt;/a&gt;&lt;span class="nb"&gt;shopt&lt;/span&gt; -s nullglob
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-37"&gt;&lt;/a&gt;
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-38"&gt;&lt;/a&gt;&lt;span class="k"&gt;for&lt;/span&gt; filename in *.pws&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-39"&gt;&lt;/a&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-40"&gt;&lt;/a&gt;    &lt;span class="nv"&gt;extra&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;extra&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; --add-extra-dicts=`&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;&lt;span class="s2"&gt;`/&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;filename&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-41"&gt;&lt;/a&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-42"&gt;&lt;/a&gt;
&lt;a name="rest_code_31f444c1b656436a8e892a642325eeae-43"&gt;&lt;/a&gt;&lt;span class="nv"&gt;$ASPELL&lt;/span&gt; &lt;span class="nv"&gt;$extra&lt;/span&gt; check &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="section" id="create-a-dictionary-from-misspelled-words"&gt;
&lt;h4&gt;Create a Dictionary from Misspelled Words&lt;/h4&gt;
&lt;p&gt;Say you have a file which contains multiple domain-specific terms.
You want to spellcheck it regularly and you want to exclude these
terms from the spellcheck &lt;em&gt;for this file only&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Keeping it separate from your general dictionary is especially useful
if these "special terms" are similar to typical typos.&lt;/p&gt;
&lt;p&gt;To easily generate this dictionary, produce a list of words that
aspell considers misspelled, and edit that list so that it contains
the "special terms" only.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://akeil.de/listings/mk-aspell-dict.sh.html"&gt;mk-aspell-dict.sh&lt;/a&gt;  &lt;a class="reference external" href="https://akeil.de/listings/mk-aspell-dict.sh"&gt;(Source)&lt;/a&gt;&lt;/p&gt;
&lt;pre class="code bash"&gt;&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-1"&gt;&lt;/a&gt;&lt;span class="ch"&gt;#!/bin/bash&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-2"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# generates an *aspell* "dictionary"&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-3"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# from a list of misspelled words found by aspell.&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-4"&gt;&lt;/a&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-5"&gt;&lt;/a&gt;&lt;span class="nb"&gt;set&lt;/span&gt; -o errexit
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-6"&gt;&lt;/a&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-7"&gt;&lt;/a&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-8"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# Configuration ----------------------------------------------------&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-9"&gt;&lt;/a&gt;&lt;span class="nv"&gt;HEADER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;personal_ws-1.1
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-10"&gt;&lt;/a&gt;&lt;span class="nv"&gt;LANG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;en
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-11"&gt;&lt;/a&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-12"&gt;&lt;/a&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-13"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# Commands ---------------------------------------------------------&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-14"&gt;&lt;/a&gt;&lt;span class="nv"&gt;ASPELL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/bin/aspell
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-15"&gt;&lt;/a&gt;&lt;span class="nv"&gt;CAT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/bin/cat
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-16"&gt;&lt;/a&gt;&lt;span class="nv"&gt;ECHO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/bin/echo
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-17"&gt;&lt;/a&gt;&lt;span class="nv"&gt;READLINK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/bin/readlink
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-18"&gt;&lt;/a&gt;&lt;span class="nv"&gt;RM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/bin/rm
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-19"&gt;&lt;/a&gt;&lt;span class="nv"&gt;SORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/bin/sort
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-20"&gt;&lt;/a&gt;&lt;span class="nv"&gt;UNIQ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/bin/uniq
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-21"&gt;&lt;/a&gt;&lt;span class="nv"&gt;WC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/bin/wc
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-22"&gt;&lt;/a&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-23"&gt;&lt;/a&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-24"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# Script -----------------------------------------------------------&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-25"&gt;&lt;/a&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; -z &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-26"&gt;&lt;/a&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-27"&gt;&lt;/a&gt;    &lt;span class="nv"&gt;$ECHO&lt;/span&gt; Error: no &lt;span class="nb"&gt;source&lt;/span&gt; specified.
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-28"&gt;&lt;/a&gt;    &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-29"&gt;&lt;/a&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-30"&gt;&lt;/a&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-31"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# normalize paths&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-32"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# aspell will not resolve shell variables or perform expansion&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-33"&gt;&lt;/a&gt;&lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;readlink -fn &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-34"&gt;&lt;/a&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-35"&gt;&lt;/a&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; ! -r &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$src&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-36"&gt;&lt;/a&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-37"&gt;&lt;/a&gt;    &lt;span class="nv"&gt;$ECHO&lt;/span&gt; Error: &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$src&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;  does not exist or is not readable
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-38"&gt;&lt;/a&gt;    &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-39"&gt;&lt;/a&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-40"&gt;&lt;/a&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-41"&gt;&lt;/a&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; -z &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-42"&gt;&lt;/a&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-43"&gt;&lt;/a&gt;    &lt;span class="nv"&gt;dest&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;.pws"&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-44"&gt;&lt;/a&gt;    &lt;span class="nv"&gt;$ECHO&lt;/span&gt; Destination not specified, using &lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="nv"&gt;$dest&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-45"&gt;&lt;/a&gt;&lt;span class="k"&gt;else&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-46"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# use `readlink` to get absolute path.&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-47"&gt;&lt;/a&gt;    &lt;span class="nv"&gt;dest&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nv"&gt;$READLINK&lt;/span&gt; -fn &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-48"&gt;&lt;/a&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-49"&gt;&lt;/a&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-50"&gt;&lt;/a&gt;&lt;span class="nv"&gt;templist&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/wordlist
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-51"&gt;&lt;/a&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-52"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# generate a wordlist from words considered misspelled by aspell&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-53"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# remove duplicates with `sort | uniq`&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-54"&gt;&lt;/a&gt;&lt;span class="nv"&gt;$ASPELL&lt;/span&gt; --rem-extra-dicts &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$dest&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; list &amp;lt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$src&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-55"&gt;&lt;/a&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;$ASPELL&lt;/span&gt; clean&lt;span class="se"&gt;\&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-56"&gt;&lt;/a&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;$SORT&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-57"&gt;&lt;/a&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;$UNIQ&lt;/span&gt; &amp;gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$templist&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-58"&gt;&lt;/a&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-59"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# generate the header for the pws file with the number of words&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-60"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# wc -l will output:&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-61"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# [numlines] [filenames]&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-62"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# (separated by space)&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-63"&gt;&lt;/a&gt;&lt;span class="nv"&gt;wordcount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nv"&gt;$WC&lt;/span&gt; -l &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$templist&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-64"&gt;&lt;/a&gt;&lt;span class="nv"&gt;$ECHO&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HEADER&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$LANG&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;wordcount&lt;/span&gt;&lt;span class="p"&gt;% *&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &amp;gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$dest&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-65"&gt;&lt;/a&gt;&lt;span class="nv"&gt;$CAT&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$templist&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &amp;gt;&amp;gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$dest&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-66"&gt;&lt;/a&gt;&lt;span class="nv"&gt;$RM&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$templist&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;a name="rest_code_4465cf97f3a04df6a8899206d0b7f73c-67"&gt;&lt;/a&gt;&lt;span class="nv"&gt;$ECHO&lt;/span&gt; Generated dictionary at &lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="nv"&gt;$dest&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;.
&lt;/pre&gt;&lt;p&gt;The command (&lt;code class="docutils literal"&gt;aspell list &amp;lt; document.txt&lt;/code&gt;)
generates a list of words that would be considered misspelled by aspell.
The command &lt;code class="docutils literal"&gt;aspell clean&lt;/code&gt; takes that list from
and cleans it up so that every line is a valid entry
for an aspell wordlist
(this might not be necessary as aspell list does probably not return
invalid words).&lt;/p&gt;
&lt;p&gt;&lt;code class="docutils literal"&gt;sort&lt;/code&gt; and &lt;code class="docutils literal"&gt;uniq&lt;/code&gt; are used to remove duplicate entries
and sort alphabetically.
The result is written to a temporary file.&lt;/p&gt;
&lt;p&gt;The last part of the script generates the header-line
for the wordlist,
using &lt;code class="docutils literal"&gt;wc &lt;span class="pre"&gt;-l&lt;/span&gt;&lt;/code&gt; to count the number of words (lines, actually).
The header is written to the dictionary file
and then the list of words is &lt;code class="docutils literal"&gt;cat&lt;/code&gt; into the dictionary file.&lt;/p&gt;
&lt;p&gt;To just update the header of a wordlist file:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_826b4c25681a4cf6b7b19d2f2106d825-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; personal_ws-1.1 en &lt;span class="sb"&gt;`&lt;/span&gt;tail -n +2 wordlist.pws &lt;span class="p"&gt;|&lt;/span&gt; wc -l&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt; &amp;gt; /tmp/wordlist.pws
&lt;a name="rest_code_826b4c25681a4cf6b7b19d2f2106d825-2"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; tail -n +2 wordlist.pws &amp;gt;&amp;gt; /tmp/wordlist.pws
&lt;a name="rest_code_826b4c25681a4cf6b7b19d2f2106d825-3"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; mv /tmp/wordlist.pws wordlist.pws
&lt;/pre&gt;&lt;p&gt;One could additionally add a &lt;code class="docutils literal"&gt;sort | uniq&lt;/code&gt; pipe to the tail-calls.
This would remove duplicates.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="create-a-wordlist-from-radicale-address-book"&gt;
&lt;h4&gt;Create a Wordlist from Radicale Address Book&lt;/h4&gt;
&lt;p&gt;Add the names of your contacts to the list of correctly spelled words.&lt;/p&gt;
&lt;p&gt;This assumes that the address book is available over the network
as CardDAV - for example via &lt;a class="reference external" href="https://akeil.de/posts/radicale/"&gt;Radicale&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This script fetches an address book from a CardDAV server
and filters out the names and nicknames of all contacts.&lt;/p&gt;
&lt;p&gt;It writes them to a wordlist named &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;~/.aspell-names.en.pws&lt;/span&gt;&lt;/code&gt;
which should be included via &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;--add-extra-dict&lt;/span&gt; in&lt;/code&gt;
in &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;~/.aspell.conf&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://akeil.de/listings/mk-aspell-names.sh.html"&gt;mk-aspell-names.sh&lt;/a&gt;  &lt;a class="reference external" href="https://akeil.de/listings/mk-aspell-names.sh"&gt;(Source)&lt;/a&gt;&lt;/p&gt;
&lt;pre class="code bash"&gt;&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-1"&gt;&lt;/a&gt;&lt;span class="ch"&gt;#!/bin/bash&lt;/span&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-2"&gt;&lt;/a&gt;&lt;span class="nb"&gt;set&lt;/span&gt; -o errexit
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-3"&gt;&lt;/a&gt;&lt;span class="nb"&gt;set&lt;/span&gt; -o nounset
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-4"&gt;&lt;/a&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-5"&gt;&lt;/a&gt;&lt;span class="nv"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"username"&lt;/span&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-6"&gt;&lt;/a&gt;&lt;span class="nv"&gt;pass&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"password"&lt;/span&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-7"&gt;&lt;/a&gt;&lt;span class="nv"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"hostname"&lt;/span&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-8"&gt;&lt;/a&gt;&lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;5232&lt;/span&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-9"&gt;&lt;/a&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://&lt;/span&gt;&lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$port&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="s2"&gt;/contacts.vcf"&lt;/span&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-10"&gt;&lt;/a&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-11"&gt;&lt;/a&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-12"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# check if the addressbook is available&lt;/span&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-13"&gt;&lt;/a&gt;curl --silent --insecure&lt;span class="se"&gt;\&lt;/span&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-14"&gt;&lt;/a&gt; --basic&lt;span class="se"&gt;\&lt;/span&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-15"&gt;&lt;/a&gt; --user &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$pass&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-16"&gt;&lt;/a&gt; --request HEAD&lt;span class="se"&gt;\&lt;/span&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-17"&gt;&lt;/a&gt; --header &lt;span class="s2"&gt;"Accept: text/vcard"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-18"&gt;&lt;/a&gt; --write-out &lt;span class="s2"&gt;"%{http_code}"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-19"&gt;&lt;/a&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-20"&gt;&lt;/a&gt; &lt;span class="p"&gt;|&lt;/span&gt; grep --silent -E &lt;span class="s2"&gt;"2[0-9]{2}"&lt;/span&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-21"&gt;&lt;/a&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-22"&gt;&lt;/a&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt; -gt &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-23"&gt;&lt;/a&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-24"&gt;&lt;/a&gt;    &lt;span class="nb"&gt;echo&lt;/span&gt; No access to adressbook at &lt;span class="nv"&gt;$url&lt;/span&gt;.
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-25"&gt;&lt;/a&gt;    &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-26"&gt;&lt;/a&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-27"&gt;&lt;/a&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-28"&gt;&lt;/a&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-29"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# Write the complete addressbook to a temp-file&lt;/span&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-30"&gt;&lt;/a&gt;curl --silent --insecure&lt;span class="se"&gt;\&lt;/span&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-31"&gt;&lt;/a&gt; --basic --user &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$pass&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-32"&gt;&lt;/a&gt; --request GET&lt;span class="se"&gt;\&lt;/span&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-33"&gt;&lt;/a&gt; --header &lt;span class="s2"&gt;"Accept: text/vcard"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-34"&gt;&lt;/a&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-35"&gt;&lt;/a&gt;&amp;gt; /tmp/vcards.vcf
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-36"&gt;&lt;/a&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-37"&gt;&lt;/a&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-38"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# Reduce to the fields we are interested in:&lt;/span&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-39"&gt;&lt;/a&gt;&lt;span class="c1"&gt;#   N:Name;Part;More;parts;;&lt;/span&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-40"&gt;&lt;/a&gt;&lt;span class="c1"&gt;#   FN:Full Name&lt;/span&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-41"&gt;&lt;/a&gt;&lt;span class="c1"&gt;#   NICKNAME: Nicky&lt;/span&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-42"&gt;&lt;/a&gt;sed -r&lt;span class="se"&gt;\&lt;/span&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-43"&gt;&lt;/a&gt; -e &lt;span class="s2"&gt;"/^(FN|N|NICKNAME):.+&lt;/span&gt;$&lt;span class="s2"&gt;/!d"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-44"&gt;&lt;/a&gt; -e &lt;span class="s2"&gt;"s/(N|FN|NICKNAME)://"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-45"&gt;&lt;/a&gt; -e &lt;span class="s2"&gt;"s/;+/\n/g"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-46"&gt;&lt;/a&gt; /tmp/vcards.vcf&lt;span class="se"&gt;\&lt;/span&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-47"&gt;&lt;/a&gt; &amp;gt; /tmp/names
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-48"&gt;&lt;/a&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-49"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# will filter the list and include only&lt;/span&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-50"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# names that would be regarded as spelling errors&lt;/span&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-51"&gt;&lt;/a&gt;~/scripts/mk-aspell-dict.sh /tmp/names /home/USERNAME/.aspell-names.en.pws
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-52"&gt;&lt;/a&gt;
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-53"&gt;&lt;/a&gt;rm /tmp/vcards.vcf
&lt;a name="rest_code_0e50759a75d8445394f397ade040a4f2-54"&gt;&lt;/a&gt;rm /tmp/names
&lt;/pre&gt;&lt;hr class="docutils"&gt;
&lt;dl class="footnote brackets"&gt;
&lt;dt class="label" id="id1"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/spellcheck-with-aspell/#id2"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://aspell.net/"&gt;http://aspell.net/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id3"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/spellcheck-with-aspell/#id4"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://github.com/WojciechMula/aspell-python"&gt;https://github.com/WojciechMula/aspell-python&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id5"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/spellcheck-with-aspell/#id6"&gt;3&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://paulbradley.org/aspell/"&gt;https://paulbradley.org/aspell/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description><category>commandline</category><category>linux</category><category>scripts</category><category>text</category><guid>https://akeil.de/posts/spellcheck-with-aspell/</guid><pubDate>Sat, 10 May 2014 10:00:00 GMT</pubDate></item><item><title>Music with MPD and beets</title><link>https://akeil.de/posts/music-with-mpd-and-beets/</link><dc:creator>Alexander Keil</dc:creator><description>&lt;div class="section" id="mpd-and-beets"&gt;
&lt;h2&gt;MPD and beets&lt;/h2&gt;
&lt;dl class="field-list simple"&gt;
&lt;dt&gt;author&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;akeil&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;date&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;2014-03-24&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;version&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;1&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;&lt;a class="reference external" href="http://www.musicpd.org/"&gt;MPD&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/music-with-mpd-and-beets/#id11" id="id12"&gt;5&lt;/a&gt; (&lt;em&gt;Music Player Daemon&lt;/em&gt;) is a music player that runs in the background
(as a daemon process).
MPD does not have a user interface but requires a separate client application
to control playback.
One interesting thing about MPD is that it can be controlled over the network.
MPD can also stream music over HTTP.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://beets.radbox.org/"&gt;beets&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/music-with-mpd-and-beets/#id3" id="id4"&gt;1&lt;/a&gt; is a command line application to manage a music library.
It downloads album/track-information and cover art from various web services
and manages your music-file's metadata ("tags").&lt;/p&gt;
&lt;p&gt;The resulting setup will be a system where you can playback and manage
music without the need for a graphical user interface
(or any graphics).
It can run on your desktop PC and be controlled by a locally installed
(GUI-) client
or it can run on a different machine and be controlled over the network.
When controlled via network, no display is required on the computer
running thee beets/MPD combo.&lt;/p&gt;
&lt;!-- TEASER_END --&gt;
&lt;div class="section" id="install-beets"&gt;
&lt;h3&gt;Install beets&lt;/h3&gt;
&lt;p&gt;The installation procedure is also described in the &lt;a class="reference external" href="https://beets.readthedocs.org/en/latest/"&gt;beets documentation&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/music-with-mpd-and-beets/#id5" id="id6"&gt;2&lt;/a&gt;.&lt;/p&gt;
&lt;div class="section" id="install-from-repository"&gt;
&lt;h4&gt;Install From Repository&lt;/h4&gt;
&lt;p&gt;Ubuntu/Linux Mint:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_b81fccaeb2174e75a3984de45cb867a2-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;#&lt;/span&gt; apt-get install beets
&lt;/pre&gt;&lt;p&gt;Arch Linux:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_a196f748a48c4876851d2d45fc32c145-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;#&lt;/span&gt; pacman -S beets
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="section" id="install-manually"&gt;
&lt;h4&gt;Install Manually&lt;/h4&gt;
&lt;p&gt;beets can also be installed manually inside a &lt;a class="reference external" href="https://virtualenv.readthedocs.org/en/latest/"&gt;virtualenv&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/music-with-mpd-and-beets/#id9" id="id10"&gt;4&lt;/a&gt;.
This allows to install it without root privileges (for a single user only)
and also keeps the system-wide python installation clean.
However, when installed like this, one must update it manually..&lt;/p&gt;
&lt;p&gt;Install &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;python-virtualenv&lt;/span&gt;&lt;/code&gt; if it is not already installed.
beets will run with &lt;strong&gt;Python 2.7&lt;/strong&gt; only, so install that version
of Python as well.&lt;/p&gt;
&lt;p&gt;On Ubuntu:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_6316aee418af478c9a7a3aa3f9203018-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;#&lt;/span&gt; apt-get install python2.7 python2.7-dev python-virtualenv
&lt;/pre&gt;&lt;p&gt;On Arch Linux&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_d28fe3b108314dd797aed1b2bcedf37c-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;#&lt;/span&gt; pacman -S python2 python2-virtualenv
&lt;/pre&gt;&lt;p&gt;Next, create a virtualenv and install beets:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_e8f11e219ddc48458d71f877290d8495-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; mkdir ~/.virtualenvs
&lt;a name="rest_code_e8f11e219ddc48458d71f877290d8495-2"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; ~/.virtualenvs
&lt;a name="rest_code_e8f11e219ddc48458d71f877290d8495-3"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; virtualenv --python python2.7 beets
&lt;a name="rest_code_e8f11e219ddc48458d71f877290d8495-4"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; beets/bin/activate
&lt;a name="rest_code_e8f11e219ddc48458d71f877290d8495-5"&gt;&lt;/a&gt;&lt;span class="gp gp-VirtualEnv"&gt;(beets)&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; pip install beets
&lt;/pre&gt;&lt;p&gt;Now beets can be executed with the &lt;code class="docutils literal"&gt;beet&lt;/code&gt; command - as long as
you have the &lt;em&gt;beets virtualenv&lt;/em&gt; activated.
To execute outside the virtualenv, link the &lt;code class="docutils literal"&gt;beet&lt;/code&gt; executable
into &lt;code class="docutils literal"&gt;~/bin&lt;/code&gt; (assuming that &lt;code class="docutils literal"&gt;~/bin&lt;/code&gt; is in your &lt;code class="docutils literal"&gt;$PATH&lt;/code&gt;):&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_379909d3ef414578b3ab89afb29bc8a0-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ln -s ~/.virtualenvs/beets/bin/beet ~/bin/beet
&lt;/pre&gt;&lt;p&gt;Updating the virtualenv-install is done like this:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_9777eff6284a462e83dc7fa0378b2e25-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; ~/.virtualenvs/beets/bin/activate
&lt;a name="rest_code_9777eff6284a462e83dc7fa0378b2e25-2"&gt;&lt;/a&gt;&lt;span class="gp gp-VirtualEnv"&gt;(beets)&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; pip install --upgrade beets
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="section" id="plugins"&gt;
&lt;h4&gt;Plugins&lt;/h4&gt;
&lt;p&gt;Beets offers several plugins.
For some of them, additional packages must be installed.&lt;/p&gt;
&lt;p&gt;Some interesting of the many &lt;a class="reference external" href="http://beets.readthedocs.org/en/latest/plugins/index.html"&gt;beets plugins&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/music-with-mpd-and-beets/#id7" id="id8"&gt;3&lt;/a&gt;:&lt;/p&gt;
&lt;dl class="field-list simple"&gt;
&lt;dt&gt;discogs&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;Use &lt;a class="reference external" href="http://www.discogs.com/"&gt;discogs&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/music-with-mpd-and-beets/#id16" id="id17"&gt;7&lt;/a&gt; as an additional source for metadata
(default is &lt;a class="reference external" href="https://musicbrainz.org/"&gt;musicbrainz&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/music-with-mpd-and-beets/#id13" id="id14"&gt;6&lt;/a&gt;).&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;lyrics&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;Fetches song lyrics.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;echonest&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;Fetches tempo (bpm) from &lt;a class="reference external" href="http://echonest.com/"&gt;echonest&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/music-with-mpd-and-beets/#id25" id="id26"&gt;11&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;fetchart&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;Fetches cover art.
Requires ImageMagick or PIL to re-size downloaded art.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;embedart&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;Embeds cover art into the music file.
Also requires &lt;a class="reference external" href="http://www.imagemagick.org/"&gt;ImageMagick&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/music-with-mpd-and-beets/#id18" id="id19"&gt;8&lt;/a&gt; or &lt;a class="reference external" href="http://www.pythonware.com/products/pil/"&gt;PIL&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/music-with-mpd-and-beets/#id20" id="id21"&gt;9&lt;/a&gt; to re-size images.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;lastgenre&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;Fetches genre from &lt;a class="reference external" href="http://www.last.fm/"&gt;last.fm&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/music-with-mpd-and-beets/#id22" id="id23"&gt;10&lt;/a&gt;
and optionally reduces genres to a canonized list.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;replaygain&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;Calculates &lt;em&gt;Replay Gain&lt;/em&gt;, using either &lt;code class="docutils literal"&gt;mp3gain&lt;/code&gt;
or &lt;code class="docutils literal"&gt;replaygain&lt;/code&gt;.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;scrub&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;Removes unwanted tags from music files.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;mbsync&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;Updates your library with new metadata
from MusicBrainz.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;missing&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;Finds missing tracks in your collection.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;duplicates&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;Finds and optionally deletes duplicate albums/tracks.&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;&lt;em&gt;Plugin requirements:&lt;/em&gt;&lt;/p&gt;
&lt;table&gt;
&lt;colgroup&gt;
&lt;col style="width: 32%"&gt;
&lt;col style="width: 32%"&gt;
&lt;col style="width: 36%"&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th class="head"&gt;&lt;p&gt;Plugin&lt;/p&gt;&lt;/th&gt;
&lt;th class="head"&gt;&lt;p&gt;Requirement&lt;/p&gt;&lt;/th&gt;
&lt;th class="head"&gt;&lt;p&gt;Install with&lt;/p&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;discogs&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;discogs-client&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;pip&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;echonest_tempo&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;pyechonest&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;pip&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;lastgenre&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;pylast&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;pip&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;mpdstats&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;python-mpd&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;pip&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;fetchart&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;imagemagick&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;(package manager)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;replaygain&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;mp3gain
replaygain&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;(package manager)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;To install the python requirements
&lt;em&gt;(assuming that beets was installed in a virtualenv)&lt;/em&gt;:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_0856321a6cb24d1eb36bbe60a67a481f-1"&gt;&lt;/a&gt;&lt;span class="gp gp-VirtualEnv"&gt;(beets)&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt; pip install discogs-client pyechonest pylast python-mpd
&lt;/pre&gt;&lt;p&gt;To install the additional packages required by beets or plugins:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_736788846ca642afa30806700b87bbf5-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;#&lt;/span&gt; apt-get install mp3gain replaygain imagemagick
&lt;/pre&gt;&lt;p&gt;A simple update script for the complete beets virtualenv:&lt;/p&gt;
&lt;pre class="code bash"&gt;&lt;a name="rest_code_a6b1144cf8fe491d8d72e0538a222a18-1"&gt;&lt;/a&gt;&lt;span class="ch"&gt;#!/bin/sh&lt;/span&gt;
&lt;a name="rest_code_a6b1144cf8fe491d8d72e0538a222a18-2"&gt;&lt;/a&gt;&lt;span class="nv"&gt;VENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.virtualenvs/beets"&lt;/span&gt;
&lt;a name="rest_code_a6b1144cf8fe491d8d72e0538a222a18-3"&gt;&lt;/a&gt;&lt;span class="nv"&gt;$VENV&lt;/span&gt;/bin/pip install --upgrade beets discogs-client pyechonest pylast
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="section" id="configure"&gt;
&lt;h4&gt;Configure&lt;/h4&gt;
&lt;p&gt;The beets config file is  &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;~/.config/beets/config.yaml&lt;/span&gt;&lt;/code&gt;.
The beets config is always user-based (this cannot be changed -
however, since version 1.3.3, an additional configuration file can
be supplied via the &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;--config&lt;/span&gt;&lt;/code&gt; option).&lt;/p&gt;
&lt;p&gt;Change the base directory where beets stores music files:&lt;/p&gt;
&lt;pre class="code yaml"&gt;&lt;a name="rest_code_78a3b234e67f4546bc81635cb4009999-1"&gt;&lt;/a&gt;&lt;span class="nt"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;/mnt/storage/music/&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;A complete list of &lt;a class="reference external" href="http://beets.readthedocs.org/en/latest/reference/config.html"&gt;beets configuration options&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/music-with-mpd-and-beets/#id27" id="id28"&gt;12&lt;/a&gt; can be found
in the beets documentation:&lt;/p&gt;
&lt;p&gt;The default config from the installation directory
can be used a a starting point for a personal config.&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_02d7a448829742c9bd7e5258fbdb2c7a-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; cp ~/.virtualenvs/beets/lib/python2.7/site-packages/beets/config_default.yaml &lt;span class="se"&gt;\&lt;/span&gt;
&lt;a name="rest_code_02d7a448829742c9bd7e5258fbdb2c7a-2"&gt;&lt;/a&gt;  ~/.config/beets/config.yaml
&lt;/pre&gt;&lt;div class="section" id="import-settings"&gt;
&lt;h5&gt;Import Settings&lt;/h5&gt;
&lt;p&gt;Files to be imported will be placed in a special "import" folder,
which is intended to contain to-be-imported files only.
beets should clean up this directory after import. Settings are:&lt;/p&gt;
&lt;pre class="literal-block"&gt;import:
    move: yes&lt;/pre&gt;
&lt;p&gt;Use the strong_rec_thresh setting to allow more or less imperfectly matched
albums to be imported without prompt:&lt;/p&gt;
&lt;pre class="literal-block"&gt;strong_rec_thresh: 0.1  # match 90% or better for auto-import&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="genres"&gt;
&lt;h5&gt;Genres&lt;/h5&gt;
&lt;p&gt;The lastgenre plugin will fetch genres from last.fm
Additionally, the last.fm genre is compared against a &lt;em&gt;whitelist&lt;/em&gt;,
to exclude some of the genres.
It is also possible to &lt;em&gt;canonicalize&lt;/em&gt; the genres to a more coarser set
of genres using a YAML-configuration file.&lt;/p&gt;
&lt;p&gt;For both options, default files are included at:&lt;/p&gt;
&lt;pre class="literal-block"&gt;~/.virtualenvs/beets/lib/python2.7/site-packages/beetsplug/lastgenre/genres.txt
~/.virtualenvs/beets/lib/python2.7/site-packages/beetsplug/lastgenre/genres-tree.yaml&lt;/pre&gt;
&lt;p&gt;Copy these to &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;~/.config/beets/&lt;/span&gt;&lt;/code&gt; and configure
beets to use these files. Edit the files as required.&lt;/p&gt;
&lt;pre class="code yaml"&gt;&lt;a name="rest_code_02821ee1c3bf441d9964ab4b92cc3f3d-1"&gt;&lt;/a&gt;&lt;span class="nt"&gt;lastgenre&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_02821ee1c3bf441d9964ab4b92cc3f3d-2"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;whitelist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;~/.config/beets/genres.txt&lt;/span&gt;
&lt;a name="rest_code_02821ee1c3bf441d9964ab4b92cc3f3d-3"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;canonical&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;~/.config/beets/genres-tree.yaml&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="id1"&gt;
&lt;h3&gt;MPD&lt;/h3&gt;
&lt;div class="section" id="install"&gt;
&lt;h4&gt;Install&lt;/h4&gt;
&lt;p&gt;Arch Linux:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_6b49adcd787e4374af568d9a4e9e1e18-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;#&lt;/span&gt; pacman -S mpd
&lt;/pre&gt;&lt;p&gt;Ubuntu/Linux Mint:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_5f8722bd18ef40e99f2fde4169c21f4a-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;#&lt;/span&gt; apt-get install mpd
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="section" id="id2"&gt;
&lt;h4&gt;Configure&lt;/h4&gt;
&lt;p&gt;The system wide config file for MPD is &lt;code class="docutils literal"&gt;/etc/mpd.conf&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;relevant settings:&lt;/p&gt;
&lt;pre class="literal-block"&gt;port                "6600"
music_directory     "/var/lib/mpd/music"
playlist_directory  "/var/lib/mpd/playlists"
db_file             "/var/lib/mpd/mpd.db"
state_file          "/var/lib/mpd/state"
sticker_file        "/var/lib/mpd/sticker.sql"
log_file            "/var/log/mpd.log"
pid_file            "/run/mpd/mpd.pid"
user                "mpd"
replaygain          "album"
metadata_to_use     "artist,album,title,track,name,genre,date,disc,albumartist"&lt;/pre&gt;
&lt;p&gt;To ensure that MPD runs smoothly, create the required files
and &lt;code class="docutils literal"&gt;chown&lt;/code&gt; them to the mpd-user.&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_8e7ba10d1cdd4273b6b0ec877998e5d4-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;#&lt;/span&gt; mkdir -p /var/lib/mpd/playlists
&lt;a name="rest_code_8e7ba10d1cdd4273b6b0ec877998e5d4-2"&gt;&lt;/a&gt;&lt;span class="gp"&gt;#&lt;/span&gt; touch /var/lib/mpd/&lt;span class="o"&gt;{&lt;/span&gt;mpd.db,state,sticker.sql&lt;span class="o"&gt;}&lt;/span&gt;
&lt;a name="rest_code_8e7ba10d1cdd4273b6b0ec877998e5d4-3"&gt;&lt;/a&gt;&lt;span class="gp"&gt;#&lt;/span&gt; chown -R mpd:mpd /var/lib/mpd
&lt;a name="rest_code_8e7ba10d1cdd4273b6b0ec877998e5d4-4"&gt;&lt;/a&gt;&lt;span class="gp"&gt;#&lt;/span&gt; touch /var/log/mpd.log
&lt;a name="rest_code_8e7ba10d1cdd4273b6b0ec877998e5d4-5"&gt;&lt;/a&gt;&lt;span class="gp"&gt;#&lt;/span&gt; chown mpd:mpd /var/log/mpd.log
&lt;a name="rest_code_8e7ba10d1cdd4273b6b0ec877998e5d4-6"&gt;&lt;/a&gt;&lt;span class="gp"&gt;#&lt;/span&gt; mkdir /run/mpd
&lt;a name="rest_code_8e7ba10d1cdd4273b6b0ec877998e5d4-7"&gt;&lt;/a&gt;&lt;span class="gp"&gt;#&lt;/span&gt; chown mpd:mpd /run/mpd
&lt;a name="rest_code_8e7ba10d1cdd4273b6b0ec877998e5d4-8"&gt;&lt;/a&gt;&lt;span class="gp"&gt;#&lt;/span&gt; ln -s /mnt/storage/music /var/lib/mpd
&lt;/pre&gt;&lt;p&gt;The &lt;strong&gt;music_directory&lt;/strong&gt; is the only directory defined in the config
that is not created during setup.
Instead we want MPD to use the same directory as beets and symlink
the music directory for MPD.
Make sure that the &lt;code class="docutils literal"&gt;mpd&lt;/code&gt; user has read permissions on all files
as well as read+execute permissions on all directories
within the music directory.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Port 6600&lt;/strong&gt; is the default for MPD.
If possible, leave this unchanged as most clients will
expect MPD to listen at this port.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;user&lt;/strong&gt; (and &lt;strong&gt;group&lt;/strong&gt;) control the permissions under which MPD is run.
On some systems, MPD must be a member of the &lt;strong&gt;audio group&lt;/strong&gt; in order
to use the sound card.&lt;/p&gt;
&lt;p&gt;The &lt;code class="docutils literal"&gt;var/lib/mpd&lt;/code&gt; directory should look as follows:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_116f12468cbc42cc8434ef1a44d5b937-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ls -l /var/lib/mpd/
&lt;a name="rest_code_116f12468cbc42cc8434ef1a44d5b937-2"&gt;&lt;/a&gt;&lt;span class="go"&gt;total 4&lt;/span&gt;
&lt;a name="rest_code_116f12468cbc42cc8434ef1a44d5b937-3"&gt;&lt;/a&gt;&lt;span class="go"&gt;-rw-r--r-- 1 mpd  mpd     0 Nov 23 18:32 mpd.db&lt;/span&gt;
&lt;a name="rest_code_116f12468cbc42cc8434ef1a44d5b937-4"&gt;&lt;/a&gt;&lt;span class="go"&gt;lrwxrwxrwx 1 root root   20 Nov 23 18:35 music -&amp;gt; /mnt/storage/music&lt;/span&gt;
&lt;a name="rest_code_116f12468cbc42cc8434ef1a44d5b937-5"&gt;&lt;/a&gt;&lt;span class="go"&gt;drwxr-xr-x 2 mpd  mpd  4096 Nov 17 19:26 playlists&lt;/span&gt;
&lt;a name="rest_code_116f12468cbc42cc8434ef1a44d5b937-6"&gt;&lt;/a&gt;&lt;span class="go"&gt;-rw-r--r-- 1 mpd  mpd     0 Nov 23 18:32 state&lt;/span&gt;
&lt;a name="rest_code_116f12468cbc42cc8434ef1a44d5b937-7"&gt;&lt;/a&gt;&lt;span class="go"&gt;-rw-r--r-- 1 mpd  mpd     0 Nov 23 18:32 sticker.sql&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;There is an extensive article in the Arch wiki on &lt;a class="reference external" href="https://wiki.archlinux.org/index.php/Music_Player_Daemon"&gt;setting up MPD&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/music-with-mpd-and-beets/#id29" id="id30"&gt;13&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="metadata"&gt;
&lt;h4&gt;Metadata&lt;/h4&gt;
&lt;p&gt;This config-setting:&lt;/p&gt;
&lt;pre class="literal-block"&gt;metadata_to_use     "artist,album,title,track,name,genre,date,disc,albumartist"&lt;/pre&gt;
&lt;p&gt;tells MPD which tags to include in its database.
You can select tracks by any of these tags.
The &lt;code class="docutils literal"&gt;albumartist&lt;/code&gt; is not enabled by default.&lt;/p&gt;
&lt;p&gt;If the &lt;code class="docutils literal"&gt;metadata_to_use&lt;/code&gt; setting is changed later, MPD must
rebuild its database for the change to take effect.
MPD will rebuild the database if you delete (or rename) the existing one.
The database is a plain text file; have a look at it.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="stream-over-http"&gt;
&lt;h4&gt;Stream over HTTP&lt;/h4&gt;
&lt;p&gt;MPD version 0.15 or up has built in HTTP-streaming.&lt;/p&gt;
&lt;p&gt;To enable it, add the following to &lt;code class="docutils literal"&gt;/etc/mpd.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;audio_output {
    type    "httpd"
    name    "HTTP Stream"
    encoder "vorbis"
    port    "8000"
    quality "7.0"
    #bitrate "128"
    format  "44100:16:2"
}&lt;/pre&gt;
&lt;p&gt;&lt;code class="docutils literal"&gt;quality&lt;/code&gt; settings for for variable bitrate (VBR) range from
-1 (poor) to 10 (high).
Alternatively, specify a constant &lt;code class="docutils literal"&gt;bitrate&lt;/code&gt;, e.g. "128".&lt;/p&gt;
&lt;p&gt;&lt;code class="docutils literal"&gt;format 44100:16:2&lt;/code&gt; means &lt;em&gt;44100khz 16 Bit Stereo&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The URL for MPD's stream is then &lt;a class="reference external" href="http://192.168.1.2:8000"&gt;http://192.168.1.2:8000&lt;/a&gt;
(if 192.168.1.2 is the IP-address of the machine MPD is running on).
For example:&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_2dfb791efaa942039f8ea4a8609c3cd3-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt; mplayer http://192.168.1.2:8000
&lt;/pre&gt;&lt;p&gt;will have &lt;code class="docutils literal"&gt;mplayer&lt;/code&gt; start playing whatever MPD currently plays.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="mpd-clients"&gt;
&lt;h3&gt;MPD Clients&lt;/h3&gt;
&lt;dl&gt;
&lt;dt&gt;&lt;a class="reference external" href="http://www.musicpd.org/clients/mpc/"&gt;mpc&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/music-with-mpd-and-beets/#id37" id="id38"&gt;17&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;a minimalist command line client.
Good for testing or quick commands.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;&lt;a class="reference external" href="http://ario-player.sourceforge.net/index.php"&gt;Ario&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/music-with-mpd-and-beets/#id31" id="id32"&gt;14&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;A desktop client with a GUI.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;&lt;a class="reference external" href="http://gmpclient.org/"&gt;GMPC&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/music-with-mpd-and-beets/#id35" id="id36"&gt;16&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;The &lt;em&gt;Gnome Music Player Client&lt;/em&gt; is another GUI client.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;&lt;a class="reference external" href="https://github.com/eonpatapon/mpDris2"&gt;mpDris2&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/music-with-mpd-and-beets/#id33" id="id34"&gt;15&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;Adapts the dbus MPRIS interface for MPD.
This makes MPD to occur in (and be controlled from) the sound menu
of your desktop environment like "normal" music players.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;&lt;a class="reference external" href="https://github.com/abarisain/dmix/"&gt;MPDroid&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/music-with-mpd-and-beets/#id41" id="id42"&gt;19&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;MPD client for Android.&lt;/p&gt;
&lt;p&gt;MPDroid has a setting to list tracks by &lt;em&gt;Albumartist&lt;/em&gt;
instead of &lt;em&gt;(Track-)Artist&lt;/em&gt;. The &lt;code class="docutils literal"&gt;albumartist&lt;/code&gt; must be included
in MPD's config as &lt;code class="docutils literal"&gt;metadata_to_use&lt;/code&gt; or this setting will not work.&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;The MPD wiki has a large &lt;a class="reference external" href="http://mpd.wikia.com/wiki/Clients"&gt;list of MPD clients&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/music-with-mpd-and-beets/#id39" id="id40"&gt;18&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="misc"&gt;
&lt;h3&gt;Misc&lt;/h3&gt;
&lt;div class="section" id="automatically-update-metadata"&gt;
&lt;h4&gt;Automatically Update Metadata&lt;/h4&gt;
&lt;p&gt;&lt;code class="docutils literal"&gt;beets&lt;/code&gt; metadata is based primarily on &lt;a class="reference external" href="https://musicbrainz.org/"&gt;MusicBrainz&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/music-with-mpd-and-beets/#id13" id="id15"&gt;6&lt;/a&gt;,
which is permanently updated.
To periodically and automatically sync your music collection,
run a script like this (e.g. weekly via &lt;code class="docutils literal"&gt;cron&lt;/code&gt;):&lt;/p&gt;
&lt;pre class="code bash"&gt;&lt;a name="rest_code_0374764f29f84b82861c22efee0191eb-1"&gt;&lt;/a&gt;&lt;span class="ch"&gt;#!/bin/bash&lt;/span&gt;
&lt;a name="rest_code_0374764f29f84b82861c22efee0191eb-2"&gt;&lt;/a&gt;&lt;span class="nv"&gt;$VENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home/username/.virtualenvs/beets
&lt;a name="rest_code_0374764f29f84b82861c22efee0191eb-3"&gt;&lt;/a&gt;
&lt;a name="rest_code_0374764f29f84b82861c22efee0191eb-4"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# sync metadata&lt;/span&gt;
&lt;a name="rest_code_0374764f29f84b82861c22efee0191eb-5"&gt;&lt;/a&gt;&lt;span class="nv"&gt;$VENV&lt;/span&gt;/bin/beet mbsync &amp;gt; /dev/null
&lt;a name="rest_code_0374764f29f84b82861c22efee0191eb-6"&gt;&lt;/a&gt;
&lt;a name="rest_code_0374764f29f84b82861c22efee0191eb-7"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# fetch coverart&lt;/span&gt;
&lt;a name="rest_code_0374764f29f84b82861c22efee0191eb-8"&gt;&lt;/a&gt;&lt;span class="nv"&gt;$VENV&lt;/span&gt;/bin/beet fetchart &amp;gt; /dev/null
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="section" id="albumart-for-mpdroid"&gt;
&lt;h4&gt;Albumart for MPDroid&lt;/h4&gt;
&lt;p&gt;MPDroid can display the cover of the currently playing song.
The cover is &lt;em&gt;not&lt;/em&gt; provided by MPD but instead&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;fetched from the internet (&lt;a class="reference external" href="http://www.last.fm/"&gt;last.fm&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/music-with-mpd-and-beets/#id22" id="id24"&gt;10&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;provided by a web server,
running on the same machine as MPD (see &lt;a class="reference external" href="https://github.com/abarisain/dmix/wiki/Album-Art-on-your-LAN"&gt;MPDroid wiki&lt;/a&gt; &lt;a class="footnote-reference brackets" href="https://akeil.de/posts/music-with-mpd-and-beets/#id43" id="id44"&gt;20&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Set up a web server like this&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_d0c4ddfe374f4ca98344d701cd23c8a6-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;#&lt;/span&gt; pacman -S nginx
&lt;a name="rest_code_d0c4ddfe374f4ca98344d701cd23c8a6-2"&gt;&lt;/a&gt;&lt;span class="gp"&gt;#&lt;/span&gt; systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; nginx
&lt;/pre&gt;&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_cc4ad484526d477390eba2e8d1e4dc85-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;#&lt;/span&gt; apt-get install nginx
&lt;/pre&gt;&lt;p&gt;The relevant nginx-configuration is this:&lt;/p&gt;
&lt;pre class="code nginx"&gt;&lt;a name="rest_code_805dc0b353c14d29bfd5f8f5e4b97371-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;http&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;a name="rest_code_805dc0b353c14d29bfd5f8f5e4b97371-2"&gt;&lt;/a&gt;    &lt;span class="kn"&gt;server{&lt;/span&gt;
&lt;a name="rest_code_805dc0b353c14d29bfd5f8f5e4b97371-3"&gt;&lt;/a&gt;        &lt;span class="s"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a name="rest_code_805dc0b353c14d29bfd5f8f5e4b97371-4"&gt;&lt;/a&gt;        &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="s"&gt;/mnt/storage/music&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a name="rest_code_805dc0b353c14d29bfd5f8f5e4b97371-5"&gt;&lt;/a&gt;        &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="s"&gt;/cover-art/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;a name="rest_code_805dc0b353c14d29bfd5f8f5e4b97371-6"&gt;&lt;/a&gt;            &lt;span class="kn"&gt;rewrite&lt;/span&gt; &lt;span class="s"&gt;/cover-art/(.*)&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt; &lt;span class="s"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a name="rest_code_805dc0b353c14d29bfd5f8f5e4b97371-7"&gt;&lt;/a&gt;            &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;
&lt;a name="rest_code_805dc0b353c14d29bfd5f8f5e4b97371-8"&gt;&lt;/a&gt;            &lt;span class="s"&gt;allow&lt;/span&gt; &lt;span class="mi"&gt;192&lt;/span&gt;&lt;span class="s"&gt;.168.1.0/24&lt;/span&gt;    &lt;span class="c1"&gt;# local network&lt;/span&gt;
&lt;a name="rest_code_805dc0b353c14d29bfd5f8f5e4b97371-9"&gt;&lt;/a&gt;            &lt;span class="s"&gt;deny&lt;/span&gt;  &lt;span class="s"&gt;all&lt;/span&gt;               &lt;span class="c1"&gt;# everybody else&lt;/span&gt;
&lt;a name="rest_code_805dc0b353c14d29bfd5f8f5e4b97371-10"&gt;&lt;/a&gt;        &lt;span class="err"&gt;}&lt;/span&gt;
&lt;a name="rest_code_805dc0b353c14d29bfd5f8f5e4b97371-11"&gt;&lt;/a&gt;    &lt;span class="err"&gt;}&lt;/span&gt;
&lt;a name="rest_code_805dc0b353c14d29bfd5f8f5e4b97371-12"&gt;&lt;/a&gt;&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="section" id="sample-beets-configuration"&gt;
&lt;h4&gt;Sample Beets Configuration&lt;/h4&gt;
&lt;p&gt;Complete sample configuration for beets:&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://akeil.de/listings/beets.config.yaml.html"&gt;beets.config.yaml&lt;/a&gt;  &lt;a class="reference external" href="https://akeil.de/listings/beets.config.yaml"&gt;(Source)&lt;/a&gt;&lt;/p&gt;
&lt;pre class="code yaml"&gt;&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-1"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# Beets configuration --------------------------------------------------------&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-2"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-3"&gt;&lt;/a&gt;&lt;span class="nt"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;/mnt/storage-1/music&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-4"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-5"&gt;&lt;/a&gt;&lt;span class="nt"&gt;import&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-6"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# write metadata to music files&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-7"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;write&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;yes&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-8"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-9"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# move imported files from source to the music directory&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-10"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;move&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;yes&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-11"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;no&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-12"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;no&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-13"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-14"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# use auto-tagging where possible&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-15"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# do not require confirmation on strong matches&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-16"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;autotag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;yes&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-17"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;timid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;no&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-18"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-19"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;resume&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;ask&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-20"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;incremental&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;no&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-21"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;none_rec_action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;ask&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-22"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;log&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-23"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-24"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;quiet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;no&lt;/span&gt;  &lt;span class="c1"&gt;# enable with command line option&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-25"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;quiet_fallback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;skip&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-26"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;default_action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;apply&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-27"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-28"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;singletons&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;no&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-29"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;languages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p p-Indicator"&gt;[]&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-30"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;no&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-31"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;flat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;no&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-32"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-33"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# use the release-date of the original (first) release of an album?&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-34"&gt;&lt;/a&gt;&lt;span class="nt"&gt;original_date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;no&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-35"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-36"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# on multi-disk releases, assign track numbers for the whole album.&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-37"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# If "per disk", make sure tracknames do not collide ("paths" setting).&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-38"&gt;&lt;/a&gt;&lt;span class="nt"&gt;per_disc_numbering&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;no&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-39"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-40"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# files matching these patterns are deleted from source after import&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-41"&gt;&lt;/a&gt;&lt;span class="nt"&gt;clutter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p p-Indicator"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Thumbs.DB"&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt; &lt;span class="s"&gt;".DS_Store"&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"*.m3u"&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt; &lt;span class="s"&gt;".pls"&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"*.jpg"&lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-42"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-43"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# files/directories matching one of these patterns are ignored during import&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-44"&gt;&lt;/a&gt;&lt;span class="nt"&gt;ignore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p p-Indicator"&gt;[&lt;/span&gt;&lt;span class="s"&gt;".*"&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"*~"&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"System&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Volume&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Information"&lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-45"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-46"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-47"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# Paths ----------------------------------------------------------------------&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-48"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-49"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# Paths and filenames for music files&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-50"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# relative to music directory&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-51"&gt;&lt;/a&gt;&lt;span class="nt"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-52"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;asciify{$albumartist}/%asciify{$album}%aunique{}/$track %asciify{$title}&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-53"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;singleton&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;Non-Album/%asciify{$artist}/%asciify{$title}&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-54"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;comp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;Compilations/%asciify{$album}%aunique{}/$track %asciify{$title}&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-55"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-56"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# replace special characters in generated filenames&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-57"&gt;&lt;/a&gt;&lt;span class="nt"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-58"&gt;&lt;/a&gt;    &lt;span class="s"&gt;'[\\/]'&lt;/span&gt;&lt;span class="p p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;_&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-59"&gt;&lt;/a&gt;    &lt;span class="s"&gt;'^\.'&lt;/span&gt;&lt;span class="p p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;_&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-60"&gt;&lt;/a&gt;    &lt;span class="s"&gt;'[\x00-\x1f]'&lt;/span&gt;&lt;span class="p p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;_&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-61"&gt;&lt;/a&gt;    &lt;span class="s"&gt;'[&amp;lt;&amp;gt;:"\?\*\|]'&lt;/span&gt;&lt;span class="p p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;_&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-62"&gt;&lt;/a&gt;    &lt;span class="s"&gt;'\.$'&lt;/span&gt;&lt;span class="p p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;_&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-63"&gt;&lt;/a&gt;    &lt;span class="s"&gt;'\s+$'&lt;/span&gt;&lt;span class="p p-Indicator"&gt;:&lt;/span&gt; &lt;span class="s"&gt;''&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-64"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-65"&gt;&lt;/a&gt;&lt;span class="nt"&gt;path_sep_replace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;_&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-66"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-67"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# filename for the album art&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-68"&gt;&lt;/a&gt;&lt;span class="nt"&gt;art_filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;cover&lt;/span&gt;  &lt;span class="c1"&gt;# results in "cover.jpg"&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-69"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-70"&gt;&lt;/a&gt;&lt;span class="nt"&gt;max_filename_length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;0&lt;/span&gt;  &lt;span class="c1"&gt;# unlimited&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-71"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-72"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-73"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# General --------------------------------------------------------------------&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-74"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-75"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# use mutliple threads during import&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-76"&gt;&lt;/a&gt;&lt;span class="nt"&gt;threaded&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;yes&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-77"&gt;&lt;/a&gt;&lt;span class="nt"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;5.0&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-78"&gt;&lt;/a&gt;&lt;span class="nt"&gt;verbose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;no&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-79"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-80"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-81"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# User Interface -------------------------------------------------------------&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-82"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-83"&gt;&lt;/a&gt;&lt;span class="nt"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;yes&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-84"&gt;&lt;/a&gt;&lt;span class="nt"&gt;list_format_item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;upper{$artist} - $album - $track. $title&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-85"&gt;&lt;/a&gt;&lt;span class="nt"&gt;list_format_album&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;upper{$albumartist} - $album&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-86"&gt;&lt;/a&gt;&lt;span class="nt"&gt;time_format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'%Y-%m-%d&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;%H:%M:%S'&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-87"&gt;&lt;/a&gt;&lt;span class="nt"&gt;terminal_encoding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;utf8&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-88"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-89"&gt;&lt;/a&gt;&lt;span class="nt"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-90"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;terminal_width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;80&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-91"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;length_diff_thresh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;10.0&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-92"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-93"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-94"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# Auto Tagger ----------------------------------------------------------------&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-95"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-96"&gt;&lt;/a&gt;&lt;span class="nt"&gt;match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-97"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;strong_rec_thresh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;0.1&lt;/span&gt;      &lt;span class="c1"&gt;# match 90% or better for auto import&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-98"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;medium_rec_thresh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;0.25&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-99"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;rec_gap_thresh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;0.25&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-100"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;max_rec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-101"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;missing_tracks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;medium&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-102"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;unmatched_tracks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;medium&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-103"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;distance_weights&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-104"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;2.0&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-105"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;artist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;3.0&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-106"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;album&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;3.0&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-107"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;media&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;1.0&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-108"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;mediums&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;1.0&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-109"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;year&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;1.0&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-110"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;0.5&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-111"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;0.5&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-112"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;catalognum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;0.5&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-113"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;albumdisambig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;0.5&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-114"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;album_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;5.0&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-115"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;tracks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;2.0&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-116"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;missing_tracks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;0.9&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-117"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;unmatched_tracks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;0.6&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-118"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;track_title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;3.0&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-119"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;track_artist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;2.0&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-120"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;track_index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;1.0&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-121"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;track_length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;2.0&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-122"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;track_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;5.0&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-123"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;preferred&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-124"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;countries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p p-Indicator"&gt;[]&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-125"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;media&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p p-Indicator"&gt;[]&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-126"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;original_year&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;no&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-127"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;ignored&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p p-Indicator"&gt;[]&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-128"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;track_length_grace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;10&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-129"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;track_length_max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;30&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-130"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-131"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-132"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# Plugins --------------------------------------------------------------------&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-133"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-134"&gt;&lt;/a&gt;&lt;span class="nt"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p p-Indicator"&gt;[&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-135"&gt;&lt;/a&gt;    &lt;span class="nv"&gt;discogs&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-136"&gt;&lt;/a&gt;    &lt;span class="nv"&gt;lyrics&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-137"&gt;&lt;/a&gt;    &lt;span class="nv"&gt;echonest_tempo&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-138"&gt;&lt;/a&gt;    &lt;span class="nv"&gt;fetchart&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-139"&gt;&lt;/a&gt;    &lt;span class="nv"&gt;embedart&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-140"&gt;&lt;/a&gt;    &lt;span class="nv"&gt;lastgenre&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-141"&gt;&lt;/a&gt;    &lt;span class="nv"&gt;scrub&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-142"&gt;&lt;/a&gt;    &lt;span class="nv"&gt;mbsync&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-143"&gt;&lt;/a&gt;    &lt;span class="nv"&gt;mpdupdate&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-144"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;#mpdstats,&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-145"&gt;&lt;/a&gt;    &lt;span class="nv"&gt;missing&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-146"&gt;&lt;/a&gt;    &lt;span class="nv"&gt;duplicates&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-147"&gt;&lt;/a&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-148"&gt;&lt;/a&gt;&lt;span class="nt"&gt;pluginpath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p p-Indicator"&gt;[]&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-149"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-150"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-151"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# Plugins Config -------------------------------------------------------------&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-152"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-153"&gt;&lt;/a&gt;&lt;span class="nt"&gt;lyrics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-154"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;yes&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-155"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;fallback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;''&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-156"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-157"&gt;&lt;/a&gt;&lt;span class="nt"&gt;echonest_tempo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-158"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;yes&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-159"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-160"&gt;&lt;/a&gt;&lt;span class="nt"&gt;lastgenre&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-161"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;whitelist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;~/.config/beets/genres.txt&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-162"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;canoncical&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;~/.config/beets/genres-tree.yaml&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-163"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;fallback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;''&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-164"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;artist&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-165"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-166"&gt;&lt;/a&gt;&lt;span class="nt"&gt;fetchart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-167"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;yes&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-168"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;maxwidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;300&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-169"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;cautious&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-170"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;cover_names&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;cover folder&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-171"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-172"&gt;&lt;/a&gt;&lt;span class="nt"&gt;embedart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-173"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;yes&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-174"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;maxwidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;300&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-175"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-176"&gt;&lt;/a&gt;&lt;span class="nt"&gt;replaygain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-177"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;yes&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-178"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;overwrite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;yes&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-179"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;albumgain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;yes&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-180"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-181"&gt;&lt;/a&gt;&lt;span class="nt"&gt;scrub&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-182"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;yes&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-183"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-184"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# required for mpdstats&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-185"&gt;&lt;/a&gt;&lt;span class="nt"&gt;mpd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-186"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;localhost&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-187"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;6600&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-188"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# none&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-189"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-190"&gt;&lt;/a&gt;&lt;span class="nt"&gt;mpdstats&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-191"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;rating&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;False&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-192"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# two ratings are calculated:&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-193"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# "rolling" based on recent development&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-194"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# "stable" based on all-time development&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-195"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# rating mix 0.0 is all "rolling", 1.0 is all "stable"&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-196"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;rating_mix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;0.75&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-197"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-198"&gt;&lt;/a&gt;&lt;span class="nt"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-199"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;$albumartist - $album - $track - $title&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-200"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;no&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-201"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;no&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-202"&gt;&lt;/a&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-203"&gt;&lt;/a&gt;&lt;span class="nt"&gt;duplicates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_94412bb89f274a4ca85a5e87b2175410-204"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;checksum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;no&lt;/span&gt;  &lt;span class="c1"&gt;# expensive&lt;/span&gt;
&lt;/pre&gt;&lt;hr class="docutils"&gt;
&lt;dl class="footnote brackets"&gt;
&lt;dt class="label" id="id3"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/music-with-mpd-and-beets/#id4"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://beets.radbox.org/"&gt;http://beets.radbox.org/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id5"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/music-with-mpd-and-beets/#id6"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://beets.readthedocs.org/en/latest/"&gt;https://beets.readthedocs.org/en/latest/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id7"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/music-with-mpd-and-beets/#id8"&gt;3&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://beets.readthedocs.org/en/latest/plugins/index.html"&gt;http://beets.readthedocs.org/en/latest/plugins/index.html&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id9"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/music-with-mpd-and-beets/#id10"&gt;4&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://virtualenv.readthedocs.org/en/latest/"&gt;https://virtualenv.readthedocs.org/en/latest/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id11"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/music-with-mpd-and-beets/#id12"&gt;5&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://www.musicpd.org/"&gt;http://www.musicpd.org/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id13"&gt;&lt;span class="brackets"&gt;6&lt;/span&gt;&lt;span class="fn-backref"&gt;(&lt;a href="https://akeil.de/posts/music-with-mpd-and-beets/#id14"&gt;1&lt;/a&gt;,&lt;a href="https://akeil.de/posts/music-with-mpd-and-beets/#id15"&gt;2&lt;/a&gt;)&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://musicbrainz.org/"&gt;https://musicbrainz.org/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id16"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/music-with-mpd-and-beets/#id17"&gt;7&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://www.discogs.com/"&gt;http://www.discogs.com/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id18"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/music-with-mpd-and-beets/#id19"&gt;8&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://www.imagemagick.org/"&gt;http://www.imagemagick.org/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id20"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/music-with-mpd-and-beets/#id21"&gt;9&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://www.pythonware.com/products/pil/"&gt;http://www.pythonware.com/products/pil/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id22"&gt;&lt;span class="brackets"&gt;10&lt;/span&gt;&lt;span class="fn-backref"&gt;(&lt;a href="https://akeil.de/posts/music-with-mpd-and-beets/#id23"&gt;1&lt;/a&gt;,&lt;a href="https://akeil.de/posts/music-with-mpd-and-beets/#id24"&gt;2&lt;/a&gt;)&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://www.last.fm/"&gt;http://www.last.fm/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id25"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/music-with-mpd-and-beets/#id26"&gt;11&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://echonest.com/"&gt;http://echonest.com/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id27"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/music-with-mpd-and-beets/#id28"&gt;12&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://beets.readthedocs.org/en/latest/reference/config.html"&gt;http://beets.readthedocs.org/en/latest/reference/config.html&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id29"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/music-with-mpd-and-beets/#id30"&gt;13&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://wiki.archlinux.org/index.php/Music_Player_Daemon"&gt;https://wiki.archlinux.org/index.php/Music_Player_Daemon&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id31"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/music-with-mpd-and-beets/#id32"&gt;14&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://ario-player.sourceforge.net/index.php"&gt;http://ario-player.sourceforge.net/index.php&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id33"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/music-with-mpd-and-beets/#id34"&gt;15&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://github.com/eonpatapon/mpDris2"&gt;https://github.com/eonpatapon/mpDris2&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id35"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/music-with-mpd-and-beets/#id36"&gt;16&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://gmpclient.org/"&gt;http://gmpclient.org/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id37"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/music-with-mpd-and-beets/#id38"&gt;17&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://www.musicpd.org/clients/mpc/"&gt;http://www.musicpd.org/clients/mpc/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id39"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/music-with-mpd-and-beets/#id40"&gt;18&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="http://mpd.wikia.com/wiki/Clients"&gt;http://mpd.wikia.com/wiki/Clients&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id41"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/music-with-mpd-and-beets/#id42"&gt;19&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://github.com/abarisain/dmix/"&gt;https://github.com/abarisain/dmix/&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt class="label" id="id43"&gt;&lt;span class="brackets"&gt;&lt;a class="fn-backref" href="https://akeil.de/posts/music-with-mpd-and-beets/#id44"&gt;20&lt;/a&gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;&lt;a class="reference external" href="https://github.com/abarisain/dmix/wiki/Album-Art-on-your-LAN"&gt;https://github.com/abarisain/dmix/wiki/Album-Art-on-your-LAN&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description><category>homeserver</category><category>linux</category><category>music</category><guid>https://akeil.de/posts/music-with-mpd-and-beets/</guid><pubDate>Mon, 24 Mar 2014 23:30:00 GMT</pubDate></item></channel></rss>