Latest Posts

System
Thursday, April 2, 2026, 18:52
public users

iBoard - ABOUT

iBoard is a little practicing project, figuring out how Elixir-development and AI comes together. So, I recently wrote this Blog-server. The content here doesn’t really matter! The project is not public (yet) but will be published at GitHub once it fulfills some basic quality standards ;-)

The iBoard Project This web application is written in Elixir with Phoenix, LiveView, TailwindCSS, DaisyUI, Ecto/Postgres, to name the most important.

ExDocs

↣ Documentation

The full dependency list reads like:

:bcrypt_elixir, "~> 3.0"
:phoenix, "~> 1.8.3"
:phoenix_ecto, "~> 4.5"
:ecto_sql, "~> 3.13"
:postgrex, ">= 0.0.0"
:phoenix_html, "~> 4.1"
:phoenix_live_reload, "~> 1.2", only: :dev
:phoenix_live_view, "~> 1.1.0"
:lazy_html, ">= 0.1.0", only: :test
:phoenix_live_dashboard, "~> 0.8.3"
:esbuild, "~> 0.10", runtime: Mix.env() == :dev
:tailwind, "~> 0.3", runtime: Mix.env() == :dev
:heroicons, github: "tailwindlabs/heroicons", tag: "v2.2.0"
:gen_smtp, "~> 1.2"
:swoosh, "~> 1.5"
:castore, "~> 1.0"
:req, "~> 0.5"
:telemetry_metrics, "~> 1.0"
:telemetry_poller, "~> 1.0"
:gettext, "~> 1.0"
:gettext_sigils, "~> 0.1.0"
:jason, "~> 1.2"
:dns_cluster, "~> 0.2.0"
:bandit, "~> 1.5"
:tzdata, "~> 1.1"
:boundary, "~> 0.10", runtime: false
:ex_doc, "~> 0.31", only: [:dev, :prod], runtime: false
:earmark, "~> 1.4"

With a big hug to this gorgeous community!

Follow the tag #iboard if you’re interested in the faith of this project.

Features so far (Buzzwords)

  • Accounts, Users (phx.gen.auth)
  • Posts, Drafts, Authors, Moderators, Likes, Followers
  • User management, invite by e-mail
  • Supports DaisyUI Themes
  • Supports Locale and Timezone
Avatar
Andi
Tuesday, September 22, 2026, 19:25
public users

Digitale Souveränität: Auch in Linz denkt jemand darüber nach

Die KPÖ Oberösterreich hat am 21. September eine Anfrage an den Linzer Bürgermeister veröffentlicht, die mich positiv überrascht hat. Gemeinderat Michael Roth-Schmida will von Dietmar Prammer wissen, wie es um die IT-Sicherheit und den Datenschutz im Magistrat Linz bestellt ist — konkret bei jenen Daten, die über Microsoft 365 verarbeitet werden.

Gefragt wird nach den Dingen, die man fragen muss:

  • welche technischen und organisatorischen Schutzmaßnahmen es gibt (Verschlüsselung, Authentifizierung, Zugriffsüberwachung),
  • wo die Daten tatsächlich liegen und ob sie die EU verlassen,
  • wie das neue Netz- und Informationssystemsicherheitsgesetz (NISG 2026) umgesetzt wird,
  • wie Sicherheitsvorfälle erkannt und gemeldet werden,
  • und ob Open-Source-Alternativen wie openDesk oder Nextcloud überhaupt geprüft wurden.

Besonders der letzte Punkt freut mich. Die Diskussion um digitale Souveränität wird in Österreich meist geführt, als wäre sie ein Luxusthema für Nerds. Dabei geht es um etwas sehr Bodenständiges: Eine Stadtverwaltung verarbeitet die Daten ihrer Bürgerinnen und Bürger. Wenn diese Daten auf Infrastruktur liegen, deren Betreiber einer fremden Rechtsordnung unterliegt, dann ist das keine technische Detailfrage, sondern eine politische.

Andere haben das längst vorgemacht — Schleswig-Holstein steigt konsequent auf offene Software um, und auch in mehreren deutschen Kommunen läuft openDesk im Echtbetrieb. Es geht also. Es kostet Arbeit, und es kostet in der Übergangsphase Nerven, aber es ist keine theoretische Übung.

Ob die Anfrage eine brauchbare Antwort bekommt, steht auf einem anderen Blatt. Anfragen dieser Art werden gerne mit Verweisen auf bestehende Verträge und Zertifizierungen beantwortet. Trotzdem: Dass die Frage in einem Gemeinderat überhaupt gestellt wird, ist ein Fortschritt. Man muss die Fragen stellen, bevor man Antworten bekommt.

Ich bin gespannt, was aus der Sitzung am 24. September herauskommt.

Avatar
Andi
Monday, September 21, 2026, 19:06
public users

Release 1.0.0

Version 1.0.0 is out. It collects a single day of work around one idea: this blog should be writable from the place where articles are actually written — a text editor and a terminal.

What is new

A JSON API. Six endpoints, authenticated with a bearer token, cover the whole life of an article: create it, upload its attachments, publish it, read it back, revise it. A key belongs to exactly one user and acts as that user, so nothing about visibility or ownership changes just because the request came from a script.

A client. post-article takes a Markdown file with a short front matter block and does the rest: create the draft, upload the images, publish on request, notify the followers only when asked. Drafts are the default, because the only step that cannot be undone is the e-mail.

Key management in the browser. Admins mint and revoke keys under API Keys, with a guide on the page itself: where to store the key, which commands to run, what each endpoint does, and where the limits are. A new key is shown exactly once — only its hash is kept.

An audit trail. Every API call is recorded with method, path, status, IP address and the name of the key that made it. Rejected calls are recorded too. Whatever a script did, the audit log knows which key did it.

Downloads. The client and the agent skill can be fetched straight from the documentation, so a new machine needs nothing but a browser.

Smaller repairs along the way

Reading the first API-written article back from the site turned up three things worth fixing:

  • The client split articles at the language separator even when that line sat inside a code example, cutting the text in half.
  • The language switch on an article drew a border around the language you were not reading, which looked like the button to press.
  • Relative timestamps only knew how to phrase the past, so a key valid for another year politely reported that it expired “just now”.

Try it

mix w_app_core.api_token create you@example.com --label laptop
post-article --check
post-article --publish article.md

This post was written in an editor and sent from a terminal.

Avatar
Andi
Monday, September 21, 2026, 18:02
public users

Writing posts from the terminal

This blog now accepts articles over a JSON API. Write the text in your editor of choice, run one command, and the post lands here — as a draft first, so nothing goes out before you have read it in its final shape.

Screenshot
Screenshot — From the Terminal into the Blog without using a browser.

Why bother

The editor in the browser is fine for a quick note. It is less fine when the text already exists as a file, when the screenshots sit in a folder next to it, or when the article is the last step of a script that has just finished a release. For those cases the blog got an API.

The shape of it

Six endpoints, all authenticated with a bearer token:

GET   /api/me                        the user behind the token
POST  /api/posts                     create a post (draft by default)
GET   /api/posts/:id                 read one of your own posts
PATCH /api/posts/:id                 revise a post, field by field
POST  /api/posts/:id/attachments     upload a file
POST  /api/posts/:id/publish         publish, optionally notify followers

A key belongs to exactly one user and acts as that user, so the same visibility and ownership rules apply as in the browser. Keys are stored hashed, expire after a year, and are minted and revoked by an admin under API Keys.

One command

The interesting part is the client. An article is a Markdown file with a small front matter block:

---
subject: The English headline
subject_de: Die deutsche Überschrift
tags: elixir phoenix
visibility: public
attachments:
  - diagram.png
---
The English body. Embed the image with {diagram.png}.

--- de ---

Der deutsche Text, wieder mit {diagram.png}.

Then:

post-article --publish article.md

The script creates the draft, uploads the attachments, publishes, and prints the URL. Leave off --publish and you get a draft to review. Add --notify and the followers get the usual e-mail — with the images already in place, which is exactly why uploading happens before publishing.

What it does not do

It does not unpublish, it does not delete, and it will not let you touch someone else’s post. Those stay in the browser, where a mis-click is visible and reversible. Revising works, though: a PATCH changes exactly the fields it is given — this very paragraph arrived that way. Every API call is written to the audit log with method, path, status, IP and the name of the key that made it, so it is always clear which script did what.

This article was posted with the command above.

Avatar
Andi
Saturday, September 19, 2026, 14:29
public users

gitpro — a GitHub project board in the terminal

I read a project board far more often than I edit one. What is on the board is something I want to know while I am standing in the checkout, working — not after opening a browser, finding the organisation, clicking the right board and hunting down the card.

So: gitpro, version 1.0.0, now on Hex.

cd ~/Projects/some-repo
gitpro

That is the whole invocation. gitpro asks git which repository the working directory belongs to, asks GitHub which Projects v2 boards that repository is linked to, and opens the cards on one of them.

Screenshot
Screenshot — A Screenshot of the TUI of gitpro, showing the startpage of the app, with a project listed.

Typing is searching

There is no mode to enter and no key to press first. The search field has the keyboard from the moment gitpro opens — every printable character is the query’s, and everything else is bound to a key the field does not want. Every word has to match somewhere in the row: its number, title, repository, column, author, milestone, labels, assignees or state.

^F opens the switches: state (open, closed, merged, draft — only the ones the board actually has), the board’s kanban columns, the labels the cards wear and the people they are assigned to, each with an empty bucket of its own. The list narrows behind the popup as the switches are set, so you can see what a switch does before you commit to it.

^I shows a card in full — title, description, state, column, repository, author, labels, assignees, milestone and dates. All of it arrived with the board, so the popup opens instantly and keeps working with the network gone. j and k walk to the next card without closing it: reading through a column is opening the first card and holding j. ^O or Enter opens the card in a browser.

How it is put together

The UI is Atui, a terminal toolkit in the shape of Phoenix LiveView: views with state, a render/2 that returns cells, and a runtime that owns the terminal.

It talks to GitHub through the gh CLI, which means there is no HTTP client, no JSON dependency and no token to configure beyond gh auth login.

Two things are deliberate. Failing happens in the terminal, not in the UI — “not a git repository”, “gh is not installed”, “this repository is not on a project board” are printed on stderr, with a non-zero exit status, before the alternate screen is opened. And everything that decides what is on screen is a pure function over data: the filters, the GraphQL decoders, the row widths, the window onto a list longer than the screen. So the interesting half of the application is tested without a terminal and without a token.

Installing

gh has to be installed and logged in with the project scope:

gh auth login
gh auth refresh -s project      # if 'project' is not already in gh auth status

Then, in one step:

mix escript.install hex gitpro

The escript lands in ~/.mix/escripts, so that wants to be on your PATH.

Not yet

An MVP, and it shows. Next up: comments and sub-issues in the detail popup, moving a card between columns from here rather than in a browser, and remembering the switches per board between runs.

GPL-3.0-or-later.