<?xml version='1.0' encoding='utf-8'?><?xml-stylesheet href='/style.xsl' type='text/xsl'?>
<feed xmlns="http://www.w3.org/2005/Atom"><id>https://kevinschoon.com/</id><title>Blog of Kevin Schoon</title><link rel="self" href="https://kevinschoon.com" /><updated>2025-05-13T01:01:01+00:00</updated><author><name>Kevin Schoon</name></author><entry><link href="https://kevinschoon.com/blog/announcing-ayllu-0.4" rel="self" /><id>https://kevinschoon.com/blog/announcing-ayllu-0.4</id><title>Ayllu version 0.4 has been released.</title><updated>2025-05-13T01:01:01+00:00</updated><content type="xhtml" xml:lang="en"><div xmlns="http://www.w3.org/1999/xhtml"><h1>Ayllu 0.4 Has Been Released!</h1>
<p><code>05/13/2025</code></p>
<p><br /></p>
<p>Ayllu release <a href="https://ayllu-forge.org/ayllu/ayllu/refs/tag/0.4.0">0.4.0</a> has
been tagged after a year of inconsistent development. This release has seen a major
<em>reduction</em> in the number of features available in Ayllu and a simplification
of it's operation. Most of the features that have been removed will eventually
make their way back into the software as time permits and can be done in a
way that is simple to maintain. Although the core functionality of Ayllu has
been minimized, two new major (unfinished) libraries were developed adjacently: 
<a href="https://ayllu-forge.org/ayllu/maitred">maitred</a>, an embedable SMTP server, 
and <a href="https://ayllu-forge.org/ayllu/papyri">papyri</a>, an OCI compliant container 
registry. See more details below.</p>
<h2>Notable Reductions</h2>
<h3>The Ayllu Binary is Now Stateless</h3>
<p>The <code>ayllu-jobs</code> binary has been temporarily removed after several iterations
of implementing sqlite based state. The binary is responsible for
doing CLOC anaylsis with <a href="https://github.com/XAMPPRocky/tokei">Tokei</a>,
calculating authorship statistics, and anything else that needs to be run at a
periodic interval. The jobs themselves have worked well but I've not been
satisfied with any approach I've used to communicate stateful data into the UI
in a way that is extensible. Ultimately my goal is to maintain an ultra fast
static web interface that can be easily extended with new kinds of repository
information. A future update will re-enable the state as opt-in likely via
an RPC interface or shared database crate.</p>
<h3>Git Blame is Removed</h3>
<p>Although the server has the ability to generate everything that is needed
to display <code>git blame</code> data I haven't been able to design UX that looks good
enough to present it so I've opted to remove the code for now. If anyone has
suggestions for how to do it in pure html/css please reach out.</p>
<h3>Simplified Themeing</h3>
<p>The themeing engine was reverted back from Tera to Askama see commit:
<a href="https://ayllu-forge.org/ayllu/ayllu/commit/5c5e94b2eff4d4181bd59ed359dabb509b0a34fc">5c5e94</a>.</p>
<h2>New Features</h2>
<h3>Web Finger Protocol Support</h3>
<p>A concept of static "membership" has been added to the server which is browsable
over the <a href="https://en.wikipedia.org/wiki/WebFinger">webfinger</a> protocol. You
can use the <code>quipu</code> binary to query resources on an any Ayllu server:</p>
<pre><code class="language-sh">quipu finger "acct:demo@example.org"
</code></pre>
<pre><code class="language-json">{
  "subject": "demo@example.org",
  "links": [
    {
      "rel": "http://webfinger.net/rel/profile-page",
      "type": "text/html",
      "href": "https://example.org/demo"
    },
    {
      "rel": "http://webfinger.net/rel/profile-page",
      "type": "text/html",
      "href": "https://example.org/@demo"
    },
    {
      "rel": "http://webfinger.net/rel/avatar",
      "href": "https://example.org/avatar.png"
    },
    {
      "rel": "http://ayllu-forge.org/rel/description",
      "properties": {
        "text": "Programmer interested free software"
      }
    }
  ]
}
</code></pre>
<pre><code class="language-sh"># Or via curl
curl 'http://localhost:10000/.well-known/webfinger?resource=acct:demo@example.org' |jq .
</code></pre>
<p>Currently the server only can share static account information from the global
configuration file but future updates will expose repository and other 
information. Eventually there are plans to implement global searchable index of 
Ayllu instances which will crawl this protocol data.</p>
<h3>Mirror Aware Repositories</h3>
<p>Ayllu will detect repositories that contain origins that have the <code>mirror</code> flag
enabled and display them as "non-canonical" sources in the UI.</p>
<h3>Multiuser Container Image</h3>
<p>The <a href="https://ayllu-forge.org/ayllu/ayllu/tree/main/containers/multiuser">multiuser</a> 
container image has been further developed and offers a simple way for managing
trusted multi-user forge environments.</p>
<h3>New Crates</h3>
<p>Two new crates <a href="https://ayllu-forge.org/ayllu/ayllu/tree/main/ayllu-shell">ayllu-shell</a> 
and <a href="https://ayllu-forge.org/ayllu/ayllu/tree/main/ayllu-keys">ayllu-keys</a> were 
added which when completed will allow a simple static configuration of user 
authorization into an Ayllu managed server.</p>
<h2>Supplemental Libraries</h2>
<h3>Maitred SMTP Server</h3>
<p>A new crate offering an embeddable SMTP server has been developed called
<a href="https://ayllu-forge.org/ayllu/maitred">maitred</a> for use in Ayllu so that it
can easily receive patches over SMTP. This project is made possible by the
low-level protocol implementation of the
<a href="https://github.com/stalwartlabs/mail-server">stalwart</a> project. Most of this
crate is functional however ARC and DMARC verification need to be finished
before we can safely handle messages. Once completed this will be implemented
as part of the <code>ayllu-mail</code> binary.</p>
<h3>Papari OCI Container Registry</h3>
<p>The <a href="https://ayllu-forge.org/ayllu/papyri">Papyri</a> OCI container registry has
started development which will provide the ability to read and write container
images directly into an Ayllu instance. This crate currently only supports
direct file system storage but a SQLite blob backend or possibly S3 compatible
store may be added. This crate is shipped with an export of Axum routes and
once integrated will allow pushing and pulling OCI manifests directly from
the web interface.</p>
<h1 />
<p>That's all for now, thanks for reading!</p></div></content></entry><entry><link href="https://kevinschoon.com/blog/building-this-website" rel="self" /><id>https://kevinschoon.com/blog/building-this-website</id><title>There is no deficiency of static website generators these days and so I had to write my own.</title><updated>2023-04-14T01:01:01+00:00</updated><content type="xhtml" xml:lang="en"><div xmlns="http://www.w3.org/1999/xhtml"><h1>Building This Website</h1>
<p>There is no deficiency of static website generators these days and so naturally
I had to write my own.</p>
<h2>Overview</h2>
<p>The main functionality of the tool will be encapsulated in a Python script
called <code>render.py</code>. Even though 
<a href="https://www.devever.net/~hl/stringtemplates#narrow">string based templates are the wrong solution</a> 
we're going to use a combination of 
<a href="https://jinja.palletsprojects.com/">Jinja</a> and markdown to generate HTML 
output. Each page will be generated by a single call to the script which will 
allow the whole thing to be ran in parallel across multiple CPUs by wiring
it together using <a href="https://ninja-build.org/">ninja</a>.</p>
<p>Here you can see an example invocation of the <code>render.py</code> script with some
annotated flags.</p>
<pre><code>./render.py \
    # the HTML template file
    --template=index.jinja \
    # this contains a map of each webpage which is available in the renderer
    # as well as page level configuration options.
    --sitemap sitemap.yaml \
    # markdown file, this one contains the text I'm currently typing!
    --content=blog/building-this-website/README.md \
    # output of the rendered HTML page
    gen/blog/building-this-website/index.html
</code></pre>
<h3>Hierarchical Sitemap</h3>
<p>I want to generate a "tree" sitemap similar to a file browser like NERDTree.
We'll pass in a YAML document to the <code>render.py</code> script and then transform
it into an XML tree.</p>
<pre><code>def _load_sitemap(path, current_path):
    by_name = dict()
    flattened = []
    with open(path, "r") as fp:
        links = yaml.safe_load(fp.read())

    def _link(links, parent=None):
        for link in links:
            flattened.append(link)
            link["parent"] = parent
            if "others" in link:
                _link(link["others"], parent=link)

    def _make_url(link):
        path = [link["name"]]
        parent = link["parent"]
        while parent is not None:
            path.append(parent["name"])
            parent = parent["parent"]
        path.reverse()
        return "/".join(path)

    _link(links, parent=None)

    for link in flattened:
        target = "/" + _make_url(link)
        if target == current_path:
            link["active"] = True
        else:
            link["active"] = False
        link["url"] = target
        by_name[target] = link

    return dict(by_name=by_name, links=links)


def _make_tree(links):
    def _populate(root, links):
        for link in links:
            elm = ET.Element("li")
            if "directory" in link and link["directory"]:
                elm.text = link["name"]
            else:
                lref = ET.Element("a", href=link["url"])
                lref.text = link["name"]
                if link["active"]:
                    lref.text = lref.text + "  &lt;"
                elm.append(lref)
            if "others" in link:
                others = link["others"]
                ul = ET.Element("ul")
                elm.append(ul)
                _populate(root=ul, links=others)
            root.append(elm)

    params = {"class": "tree"}

    root = ET.Element("ul", **params)
    _populate(root=root, links=links)
    return ET.tostring(root).decode()
</code></pre>
<h3>Dithering Engine</h3>
<p>Images can be reduced in size with <a href="https://en.wikipedia.org/wiki/Dither">dithering algorithms</a>
and they also look very cool. The idea for this was inspired by <a href="https://solar.lowtechmagazine.com/">LOW←TECH MAGAZINE</a>.</p>
<h3>Spell &amp; Grammar Checking</h3>
<p>I can implement some basic spell checking against the website with <a href="http://aspell.net/">aspell</a>.</p>
<pre><code class="language-sh">find content -name '*.md' -exec aspell --mode=markdown check {} \;
</code></pre>
<h3>Filesystem "Watch" Support</h3>
<p>We'll use the native <a href="https://en.wikipedia.org/wiki/Inotify">inotify</a> via the
<a href="https://man7.org/linux/man-pages/man1/inotifywatch.1.html">inotifywatch</a> tool
which is available on all Linux distributions.</p>
<pre><code class="language-sh">#!/bin/sh

# the actual command we want to run each time any file changes in the project
BUILD_CMD="$@"

# some pretty colorized output
_do_build() {
    $BUILD_CMD &amp;&amp; {
        echo -e "\033[32;1;4mSuccess\033[0m"
    } || {
        echo -e "\033[31;1;4mFailure\033[0m"
    }
}

# build the project the first time you start up the watch
_do_build

# watch all the files in the project except for the output directory
find . -type d -not -path './gen*' -printf '%p ' | xargs inotifywait \
    -m -e close_write \
    --format %e/%f |
    while IFS=/ read -r events file; do
        echo "file $file modified, rebuilding"
        _do_build
    done
</code></pre>
<p>Now we can test the script by launching it <code>scripts/watch.sh build.py</code>.</p>
<p>You can checkout the source for the whole site 
<a href="https://ayllu-forge.org/web/kevinschoon-dot-com">here</a>.</p></div></content></entry></feed>