// EXAMPLES

Examples of projects and proof-of-concepts.

Visit my FoGit page for publicly available repositories.

All of these small projects/POCs are hosted on an old Lenovo box at my home together with many private whitelisted things. In front of it is an Ubuntu VPS that only acts as a fixed IP address and reverse proxy via Caddy to the Lenovo box through a secure WireGuard protocol.

Description

Generative AI is excellent at summarizing text, images, and video. However, I have one problem: I don't trust Anthropic and OpenAI at all. I mean, not at all all. And others shouldn't either, if you ask me. I believe that before long, small local models will be integrated with structured and unstructured data, allowing them to search through large document collections and provide private and concise answers. Think, for example, of large legal archives, old map and image collections, and news archives, where answers can be found across jpg, mp4, pdf, xlsx, csv, txt, ppt, and so on and so on. We're talking about thousands of files that generative AI can answer questions about through a vector database.

There are several reasons why we can't trust a handful of companies to process all our data, and there are also fields where it simply isn't an option to send data and queries to Anthropic or OpenAI. But that's a political discussion that I won't get into here.

Anyways, that's why I've developed a generative AI RAG prototype that can do some of this. The application is called RAGGo, RAG because it uses Retrieval Augmented Generation, and it's written in Go. Original, huh? RAG-Go. RAGGo. Anyway, RAGGo is hosted on an old desktop computer at my home. It indexes documents and images and allows you to have a conversation with them using natural language. Everything can run on an (air-gapped, with no network connection) Linux server on your own IT infrastructure. Data never leaves your server, never passes through a third-party cloud provider, and is completely in your hands. There are no unknown monthly API bills or hidden licenses. Once RAGGo is set up, the system runs without any additional cost. Here, we run a small embedding model and Gemma 4 locally.

RAGGo supports the following document formats:
pdf, docx, odt, xlsx, pptx, html, htm, epub, txt, md

RAGGo supports the following image formats:
png, jpg, jpeg, webp, gif

Documents with structure (such as pdf, docx, odt, xlsx, pptx, html, htm, epub) are analyzed semantically while preserving page numbers, headings, tables, and lists as metadata that preserves context. Plain text files and markdown files are split into semantically meaningful chunks rather than being cut in the middle of a sentence. Images are processed via AI captioning, which describes their contents, and are then indexed alongside the other documents, making visual data searchable using natural language as well.

Built on open tools such as Go, PostgreSQL with pgvector, and Ollama, the setup is very simple and easy to reproduce and maintain. A web interface provides chat and document management. For companies and institutions that manage large document collections, this creates a searchable knowledge base that remains easy to manage with predictable costs, without being dependent on external AI providers.

I've added some files from snar.fo (everything under "kvøði", "ævintyr", and "sagnir") and Friends Wikipedia, try it out :-)

Guide

Log in – To use RAGGo, log in using the link below.
Username: raggo
Password: oKfE!DHecTgJM9zPjjgfqL#mt59#n5G4C3gsjF4f

Add documents – Click the + button below the input field. Select a supported file (PDF, DOCX, ODT, XLSX, PPTX, HTML, EPUB, TXT or Markdown). The file is automatically parsed and indexed and becomes searchable within seconds. You'll receive a confirmation showing the filename and the number of chunks created.

Add images – Click the image button next to the + button. Select an image file (PNG, JPG, JPEG, WEBP or GIF) and write a short description of the image. The description makes the image searchable. If a vision model has been configured, you can click Auto-caption to generate the description automatically.

Ask questions – Type your question into the input field and press Enter (use Shift+Enter for a line break). RAGGo retrieves the most relevant passages from the indexed documents and images and generates an answer based on that material. Every response cites the source file and, when applicable, the page number for structured documents.

View indexed files – Click Documents in the navigation menu to see an up-to-date table containing everything in your collection: filenames, file types, chunk counts, ingestion timestamps, and detected structural features such as tables and lists. The list updates automatically.

Tech Stack
▸ Go – with third-party libraries tsawler/tabula, openai/openai-go, and fsnotify/fsnotify
▸ Postgres 18 + pgvector – PostgreSQL backend with pgvector as the vector store
▸ Taskfile – used for build, deployment, and orchestration commands
▸ Ollama – inference server
▸ AI components – conversational model as the primary chat interface; embedding model that converts text chunks into vectors for similarity search; multimodal model that accepts images as input and produces text output; query rewriter that rewrites previous user prompts into an optimized search query
▸ AI models – gemma4, nomic-embed-text
▸ CSS
▸ HTML
▸ JavaScript

Source code
Private
Username
raggo
Password
oKfE!DHecTgJM9zPjjgfqL#mt59#n5G4C3gsjF4f
Link
RAGGo
Description

As the old man said: It's pretty great to show that you can pull data from an API and visualise it on a website where users can filter the view. Here we're using fisheries statistics, updated once a month. So we can kind of see what's going on, right? I'm calling this application 'hagfisk'. Basically, statistics (FO = hagtøl) + fish. Hagfisk. I've since discovered that hagfish is also an incredibly gross and slimy fish.

But yeah, I went with the name anyway.

Anyways, there's not much more to say: fetch data from the Hagstova API, store it in a local DuckDB database on the server, expose it through an API to the web frontend, and visualise the data.

Guide

Go to the page, have a look around, and filter.

Technology
▸ Rust - with libraries axum, serde, tokio, tower-http, reqwest, rust-embed
▸ DuckDB - local OLTP database
▸ HTML
▸ JavaScript
▸ CSS
▸ ECharts - visualisation library

Source Code
FoGit Repos
Link
Hagfisk
Description

Web app for time registration and invoice generation to PDF. I use this app myself in my daily work as a consultant. I built it because there is no app that does exactly this on the Faroese market. Besides, I am also too stingy to pay some company a monthly fee for this. This prototype runs only in the browser without any backend, so all data is deleted every 20 minutes.

Otherwise, in the version I use, a backend API is connected to a VPS that reverse proxies calls through Caddy to my home server, where a SQLite database receives all CRUD operations. This version is of course IP-whitelisted, so you cannot use it ;-)

Guide

1. Enter information about your company on the [SETTINGS] page (there are default values if you cannot be bothered with this)

2. On the [TIMER] page, create Company, which is the company you will work for. Enter name, address, email, phone number and standard hourly rate. All of this is used to calculate the final price and as information for invoice generation.

3. Then create an Assignment, which is a larger chunk of work. It could, for example, be Develop backend for the new website at example.com. A Company can have several Assignments. Here you can also give a longer description of the task and a special price for exactly this Assignment. If no price is given, it is the standard price for the company that was just created.

4. Then create a Task. This is a sub-chunk of work under an Assignment. An Assignment can own many Tasks. So we create a Task called Develop GET customer endpoint or something.

5. From here you can then choose to attach time to a Task, either by starting Timer or by entering a Manual entry.

6. Now the assignment is finished, and you need to send an invoice to Company. Press Generate invoice (PDF). On this page you see hours, amount and price. Write a short description of this Assignment in Comment. Here you can also attach invoice lines that are not hours but some unit. So you can, for example, add a router for 500 kr. each or something. Well, when you are ready, press Generate PDF, and tada, a beautiful invoice, ready to send to Company. Phew, that was a lot, but try it?

Tech stack
▸ Go - the web server, backend code, templating language etc.
▸ HTMX - used to do CRUD operations
▸ GNU Make - used to automate building the binary and deployment to a remote server via ssh with one command.
▸ CSS
▸ HTML
▸ JavaScript
▸ SQLite
Source code
FoGit repos
Link
zeitkort
Description

I have wanted to build a project for a long time that streams events from a database to an event consumer. In other words, using what is called CDC (Change Data Capture), where changes are read from the database’s transaction log or replication mechanism and forwarded to a consumer.

CDC/log-based replication is based on the fact that the database already records changes in order to support recovery and replication. In PostgreSQL, this is for example WAL and logical replication/logical decoding; in MariaDB/MySQL, it is the binlog; and in SQL Server, CDC exists as a built-in feature. These mechanisms can capture INSERT, UPDATE, and DELETE operations in tables, which makes this an efficient way to stream changes from a database without requiring the application itself to emit extra events.

I think Debezium is one of the strongest solutions when it comes to streaming data between a database and a consumer, such as Apache Kafka or NATS. Debezium can read changes from the database and forward them as events with metadata. It is written in Java and is easy to deploy as a container on a Linux server.

I have often wrestled with Apache Kafka, and just as often I end up with no result and a headache after a long day of trial and error. That is why I chose to use NATS this time instead. NATS is written in Go and is a binary, unlike that Kafka Java monster with a thousand moving parts.

Anyway, long story short: this is an inventory management system. On the page, you can perform CUD operations, which result in SQL DML against the PostgreSQL database. CDC captures the changes from the log, Debezium sends the change together with metadata to NATS, and from there multiple systems could subscribe to real-time data from the database.

The advantage of this is that the operational system is decoupled from downstream usage of the live data. The inventory system only has to do its normal work against the database, while other systems can consume the changes without being directly tied to the application itself. This could, for example, be a BI dashboard showing live inventory data, or other systems and employees that need to make decisions based on live operational data.

Guide

In the demo, the inventory system is shown on the left and the CDC trace on the right. Select a product and make a change, such as sell, restock, rename/move, or delete. Once the change is made, the right side shows how it moves through the flow: PostgreSQL -> Debezium -> NATS JetStream -> browser. At the bottom, the event is shown as raw JSON and an audit trail, so you can see both the before and after state of the change.

Tech stack
▸ Go - web server, API endpoints, NATS consumer, and SSE event stream.
▸ PostgreSQL - inventory database with logical replication enabled.
▸ Debezium Server - captures database changes from PostgreSQL and publishes them as events.
▸ NATS JetStream - event streaming layer between Debezium and the Go backend.
▸ Taskfile - used for build, deployment, and orchestration commands.
▸ Podman - runs the development and production infrastructure containers (NATS, PostgreSQL, Debezium).
▸ Server-Sent Events - streams CDC events live from the Go backend to the browser.
▸ HTML
▸ CSS
▸ JavaScript
Source code
FoGit repos
Link
inventory-fló
Description

Web API for retrieving data from backend, written in Go. This is an imagined example of registering abuse to an API endpoint, both as a registration tool and for later analysis.

Deployed as just one binary without any dependencies or runtime environment. The entire frontend is embedded in the binary. The project uses only the Go standard library, no external dependency.

Guide

See the technical guide on the link below.

Tech stack
▸ Go - 100% Golang std lib: html/template, net/http, flag, crypto
▸ Taskfile - process automation, builds and deployment
▸ Reflex - fast rebuild and start of the binary when files in the repo change
▸ Rsync - for syncing to production
▸ CSS
▸ HTML
▸ JavaScript
Source code
FoGit repos
Link
Abuse Registration API
Description

Web app developed for Landsverk. Used for job matching and similar things.

Free hosting and deployment to GitHub Pages with Hugo/Go as a static site generator that is integrated with a CI/CD pipeline. One PR into main updates the whole page.

Guide
Find out which profile you are by clicking your way through Starvskumpass.
Tech stack
▸ Go - Hugo uses Go html/template as templating language
▸ Hugo - Static Site Generator written in Go
▸ JavaScript - quiz logic
▸ CSS
▸ HTML
Source code
Github repos
Link
Starvskumpass link
Description

A simple POC of a chat function written in Rust. Rust fascinates me, so I wanted to try to build and deploy a Rust project.

We have a back office where some supporter waits for messages while he eats chips.

Then two users write into the chat, and the supporter can chat with both users at once.

Going forward, I plan to connect the back office to a backend, where a supporter can attach metadata to chats, so that in the future one can make quantitative analyses. Also, if one stores the conversations, one can make sentiment analyses or qualitative analyses.

But I have children and a house and a job I have to take care of, so...

Guide

This POC deletes all data/chats every 20 minutes and has rate limiting on req/resp, so my home server will not be smashed.

Go to the link below. First choose the supporter, go back to the front page in a new tab, and then choose user.

Give the user some name and the chat starts. Write something and the supporter sees that a conversation starts. Now you can chat with yourself.

But what if you want to chat even more with yourself? Create a new user and now the supporter has two chats open.

Tech stack
    ▸ Axum - web framework in Rust. Takes care of routing and middleware and all that there. About the same as Gin in Golang and FastAPI in Python
    ▸ Tokio - the thing one always uses when one needs async await in Rust
    ▸ Serde - serialization/deserialization from JSON to Rust structs
    ▸ GNU Make - used to automate building the binary and deployment to a remote server via ssh with one command.
    ▸ CSS
    ▸ HTML
    ▸ JavaScript
Source code
Private
Link
User to Support Chat
Description

This website is hosted on a Lenovo box at my home.

Guide
N/A
Tech stack
▸ Go - 100% Golang std lib, except for a fairly standard dot-env reader, joho/godotenv
▸ Taskfile - process automation, builds and deployment
▸ Reflex - fast rebuild and start of the binary when files in the repo change
▸ Rsync - for syncing to production
▸ CSS
▸ HTML
▸ JavaScript
Source code
Private
Link
fló.fo