Compare commits

...

7 Commits

Author SHA1 Message Date
Lol3rrr
0596c18939 Add the homelab-ipv4-epxpose series
All checks were successful
Build Docker Image / docker (push) Successful in 24s
2026-10-18 22:17:07 +02:00
Lol3rrr
8e74981e63 Finish up the streaming post
All checks were successful
Build Docker Image / docker (push) Successful in 35s
2026-10-18 22:13:11 +02:00
Lol3rrr
a399bb04fd Get started on the reversi series
All checks were successful
Build Docker Image / docker (push) Successful in 12s
2026-10-04 19:55:12 +02:00
Lol3rrr
b57796b7b5 Add configuration to have post series for things like quickfacts and upcoming reversi
All checks were successful
Build Docker Image / docker (push) Successful in 19s
2026-10-04 19:35:12 +02:00
Lol3rrr
15377b9c66 Mostly finish the streaming post
All checks were successful
Build Docker Image / docker (push) Successful in 26s
2026-10-04 13:15:01 +02:00
Lol3rrr
ec47643c32 Get started on adding a post for servicemesh
All checks were successful
Build Docker Image / docker (push) Successful in 1m9s
2026-09-28 00:19:25 +02:00
Lol3rrr
529cf82206 Update some stuff on the streaming post 2026-09-28 00:18:39 +02:00
13 changed files with 234 additions and 20 deletions

View File

@@ -6,9 +6,11 @@ description = "Deploying my new IPv4 expose setup"
[taxonomies] [taxonomies]
categories = ["Homelab"] categories = ["Homelab"]
tags = ["Homelab", "IPv4", "IPv6"] tags = ["Homelab", "IPv4", "IPv6"]
series = ["homelab-ipv4-expose"]
[extra] [extra]
toc = true toc = true
series_order = 2
+++ +++
Implementing the ideas and conclusions from my initial post about exposing my IPv6 Homelab to IPv4 clients ([Homelab IPv4 Expose](@/homelab-ipv4-expose/index.md)) in the real world and deploying the entire system. Implementing the ideas and conclusions from my initial post about exposing my IPv6 Homelab to IPv4 clients ([Homelab IPv4 Expose](@/homelab-ipv4-expose/index.md)) in the real world and deploying the entire system.

View File

@@ -6,9 +6,11 @@ description = "How I expose my IPv6 only Homelab for IPv4 clients"
[taxonomies] [taxonomies]
categories = ["Homelab"] categories = ["Homelab"]
tags = ["Homelab", "IPv4", "IPv6"] tags = ["Homelab", "IPv4", "IPv6"]
series = ["homelab-ipv4-expose"]
[extra] [extra]
toc = true toc = true
series_order = 1
+++ +++
How I plan to expose the services running in my homelab to IPv4 users despite not having a publicly routable IPv4 address/subnet. How I plan to expose the services running in my homelab to IPv4 users despite not having a publicly routable IPv4 address/subnet.

View File

@@ -0,0 +1,19 @@
+++
title = "Nomad + Consul ServiceMesh"
date = 2026-10-01
description = "TODO"
draft = true
[taxonomies]
categories = ["Homelab"]
tags = ["Homelab", "Nomad", "Consul"]
[extra]
toc = true
+++
TODO
<!-- more -->
TODO2

View File

@@ -7,9 +7,11 @@ draft = true
[taxonomies] [taxonomies]
categories = ["Programming"] categories = ["Programming"]
tags = ["Factorio", "Rust"] tags = ["Factorio", "Rust"]
series = ["quickfacts"]
[extra] [extra]
toc = true toc = true
series_order = 3
+++ +++
The first real entry in my series about my quickfacts project. The first real entry in my series about my quickfacts project.

View File

@@ -7,9 +7,11 @@ draft = true
[taxonomies] [taxonomies]
categories = ["Programming"] categories = ["Programming"]
tags = ["Factorio", "Rust"] tags = ["Factorio", "Rust"]
series = ["quickfacts"]
[extra] [extra]
toc = true toc = true
series_order = 2
+++ +++
Testing Testing

View File

@@ -7,9 +7,11 @@ draft = true
[taxonomies] [taxonomies]
categories = ["Programming"] categories = ["Programming"]
tags = ["Factorio", "Rust", "Quickfacts"] tags = ["Factorio", "Rust", "Quickfacts"]
series = ["quickfacts"]
[extra] [extra]
toc = true toc = true
series_order = 1
+++ +++
A small introduction to my ongoing quickfacts project. A small introduction to my ongoing quickfacts project.

View File

@@ -0,0 +1,28 @@
+++
title = "Reversi - Intro"
date = 2026-10-31
description = "An introduction to reversi and my goals of this series"
draft = true
[taxonomies]
categories = ["Programming"]
tags = ["Rust", "Reversi", "Optimization"]
series = ["reversi"]
[extra]
toc = true
series_order = 1
+++
So this first entry in the series will be an introduction into the game reversi, and the modified rules for this case, and talk a bit about the context and goals of this series overall.
<!-- more -->
# Motivation
TODO
# Reversi the Game
TODO
# Goal
TODO

View File

@@ -1,32 +1,106 @@
+++ +++
title = "Streaming setup" title = "Streaming for Teamspeak3"
date = 2026-06-01 date = 2026-10-18
description = "Custom streaming setup to complement teamspeak" description = "Custom streaming setup to complement teamspeak"
draft = true
[taxonomies] [taxonomies]
categories = ["Homelab"] categories = ["Homelab"]
tags = ["Homelab"] tags = ["Homelab", "Teamspeak3"]
[extra]
toc = true
+++ +++
## Motivation Setting up a custom streaming service for me and my friends to have screensharing on teamspeak.
Recently my friends and I decided to move away from discord as much as we can.
The voice stuff was easy, just self host a Teamspeak3 instance on my Homelab and you are good to go
(with arguably better results).
However we often had screenshares running in our discord, especially when everyone is just chilling <!-- more -->
in the same voice channel and someone shares their game/whatever in case anyone wants to check in.
But as the experienced teamspeak user might be aware, TS3 does not support screen sharing or anything
the like natively.
## OvenMediaEngine + OBS # Motivation
Luckily I found OvenMediaEngine, which is an entire software setup to basically host your own streaming Recently my friends and I decided to move away from discord as much as we can, for a variety of reasons.
platform. This is obviously overkill for our goal, but works better than I would have expected at first. Replacing the simple voice stuff was easy, as I can just self host a Teamspeak3 instance on my servers and we are good to go (with arguably better results).
## Teamspeak3 Plugin However we were often using screensharing / streaming on discord as well, whenever we were just hanging out and doing different things or just to quickly share something.
To make the setup for my friends easier and to add some form of security/authentication, I wrote a small But as anyone that has used teamspeak before might know, Teamspeak3 does not support screensharing or anything the like natively.
teamspeak plugin, which generates the credentials and urls needed to both stream yourself and watch streams.
## Custom Website But given that teamspeak allows for creating your own plugins and makes it relatively easy to distribute them, I thought it might be interesting to try and basically add this functionality myself using a plugin.
## Conclusion # The Plan
So the plan is to have a teamspeak plugin, which we use to authenticate users, automate the streaming setup and allow for easily watching streams.
In practice that means you are required to log into my self-hosted auth server, which both ensures that you are actually allowed to stream and allows me to send dynamic information about the streaming setup to the client (like some secret key to use for generating the actual streaming key later on).
If someone wants to start streaming, the plugin provides you the URL and stream-key for manual use, but can also start OBS itself and properly configure the streaming details, if the OBS websocket server has been enabled before.
As a viewer you can just click on someone in teamspeak and click on "watch", which opens up a new window containing just the stream.
# OvenMediaEngine
OvenMediaEngine[^OvenMediaEngine] is a project I found, that allows you to basically setup your own streaming platform with all kinds of features.
However for this use-case the setup will be limited to the most basic configuration of just directly rebroadcasting the incoming streams, without any reencoding or the like.
So it accepts streams over rtmp from streaming sources, like OBS, and then listens for any "consumer" and forwards the stream using WebRTC over UDP to them.
This allows for low-latency streaming, which is very important as you are likely talking to the person streaming and any large delays in the stream would greatly reduce the overall enjoyability of this plugin.
# OBS
OBS[^OBS] in this case is just the default choice for streaming, as it is open-source and relatively easy to use by everyone.
The main requirement is that it needs to be able to stream over rtmp, but a benefit of using OBS is that you can (once enabled) interact with OBS programmatically over a websocket server.
This means that users do not have to manually enter the stream credentials every time they want to start the stream, but instead the plugin can actually configure these settings for the user.
# Teamspeak3 Plugin
The plugin itself is relatively simple, as all it has to do is authenticate the user and give them two new buttons for starting a stream and watching streams.
Teamspeak plugins[^ts3plugin] in general are relatively straight forward to build, as they are just dynamically linked library, exposing a specific set of functions, which are loaded at runtime by the teamspeak client.
To ease the development of the plugin itself, I actually also wrote my own small rust crate that provides a nicer API for specific features.
This includes easier creation of menus and their corresponding handlers and providing a tracing subscriber that prints out the messages to the teamspeak log, for easier debugging.
The authentication works very roughly, by starting a local webserver and providing a link to my auth server.
This is a simple OAuth flow, where the user gets redirected to the local webserver of the plugin, allowing it to get all the information it needs.
This information includes both the endpoint to stream to and a secret key, which gets used together with the users ID to generate the stream-key, which authenticates them against OvenMediaEngine.
Starting streaming is relatively simple, the plugin uses the secret key and the users current teamspeak ID and generates the stream-key to use in OBS or other programs.
For manual usage, the plugin outputs the streaming endpoint and the stream-key in a message to the user itself.
For automatic usage, the plugin tries to launch OBS and connect to it over the websocket server, if that fails it will just ignore the failure, but if it can connect it will automatically update the streaming settings to the correct information.
Watching is the easiest part, as the plugin just launches the bundled custom viewer, with the teamspeak ID of the person they want to watch and the secret-key.
The custom viewer will then take care of the authentication for watching the stream and actually begin the video player, which is actually using the provided player implementation of OvenMediaEngine.
## Custom Viewer
The custom viewer, is based on a react site statically bundled into an electron app.
I chose this approach, as OvenMediaEngine provides a library for embedding a stream in a website, which handles all the player and related logic making it very easy to use.
One detail is that it requires some extra information provided as command line arguments, like the ID of the stream to watch and a secret key to authenticate against OvenMediaEngine.
# Limitations
Although the current system works, there are still quite a bit of short comings.
## IPv6 only
OvenMediaEngine uses direct UDP streams between the server and the viewers, thus the server requires a publicly reachable address.
However in my homelab setup, where this is hosted, I natively only have public IPv6 address and no public IPv4, meaning that only people with IPv6 can use the current setup.
This could be solved by either getting ahold of some public IPv4 addresses for my servers (very unlikely) or I could maybe use some of the more advanced features of OvenMediaEngine, which includes streaming to "satelite" servers, which then serve the viewers.
Basically instead of the source server streaming to the viewers directly, it forwards the stream to these other servers, which then forward it to the viewers.
This would allow me to have an external server with both IPv4 and IPv6, which can basically proxy all the streams, but it is unlikely that I will do this as it would incur more costs and maintenance for limited benefits.
## OBS not bundled
Everything for using the plugin to watch streams is already bundled in the single installer, to make it easy to use for everyone.
However I have not found a good way to achieve this with OBS and especially certain OBS settings.
Like I mentioned before, the websocket server that the plugin is using to configure OBS, has to be enabled before the plugin can actually use it, all parameters about the websocket server can be configured via the cli apart from actually enabling it.
This means, that users have to start OBS manually at least once to enable the websocket server, before the plugin can programmatically interact with OBS, which just creates some really unexpected pain-points in my experience.
## Stream IDs can become mismatched
Currently the plugin is just using the teamspeak ID of a user as its stream ID, as that makes it very easy to determine the stream ID of anyone a user wants to watch.
However the teamspeak ID changes whenever a user disconnects from the teamspeak server, so if someone:
1. starts a stream
2. disconnects
3. reconnects
Then noone will be able to view their currently running stream, as it was started under their old teamspeak ID which has now changed.
# Conclusion
Overall I am pretty happy with the setup and plugin right now.
Obviously there are things to improve and its kind of hard to explain to someone using it for the first time, but once everything has been setup once, it actually works suprisingly well.
The main issue currently is the limitation to IPv6, as I have friends whose internet provider does not give them an IPv6 address for some reason, meaning they cannot use the streaming.
Apart from that I would generally like to improve the viewer itself some more, as it is currently just the bare minimum to get something working, but I have a couple of ideas to improve the viewing experience.
And I would really love to properly open-source the project, however since the plugin currently has the auth server hardcoded into it, it would not really benefit anyone, but I am thinking about how I might be able to remove the blockers for open-source.
# References
[^OvenMediaEngine]: [OvenMediaEngine](https://ovenmedia.com/)
[^OBS]: [OBS](https://obsproject.com/de)
[^ts3plugin]: [Teamspeak3 Plugin SDK](https://github.com/teamspeak/ts3client-pluginsdk)

View File

@@ -13,6 +13,14 @@
</svg> </svg>
{% if page.date %}Published: {{ page.date | date(format="%F") }}{% endif %} {% if page.date %}Published: {{ page.date | date(format="%F") }}{% endif %}
{% if page.taxonomies.series %}
<br>
{% set series_name = page.taxonomies.series | first %}
{% set series_term = get_taxonomy_term(kind="series", term=series_name) %}
Part {{ page.extra.series_order }} of the <a href="{{ series_term.permalink }}">{{ series_name }}</a> series
{% endif %}
{% endmacro meta %} {% endmacro meta %}
{% macro page_in_list(page) %} {% macro page_in_list(page) %}

20
templates/page.html Normal file
View File

@@ -0,0 +1,20 @@
{% extends "radion/templates/page.html" %}
{% block header %}
{{ super() }}
{% if page.taxonomies.series %}
{% set series_name = page.taxonomies.series | first %}
{% set series_term = get_taxonomy_term(kind="series", term=series_name) %}
<div class="series">
<a href="{{ series_term.permalink }}">
Series: {{ series_term.name }}
</a>
{% if page.extra.series_order %}
<span> · Part {{ page.extra.series_order }}</span>
{% endif %}
</div>
{% endif %}
{% endblock header %}

View File

@@ -0,0 +1,29 @@
{% extends "index.html" %}
{% block title %}Browse Series{% endblock title %}
{% block og_title %}Browse Series{% endblock og_title %}
{% block twitter_title %}Browse Series{% endblock twitter_title %}
{% block meta_description %}
<meta name="description" content="Discover articles organized by series in this site index." />
<meta property="og:description" content="Discover articles organized by series in this site index." />
<meta property="twitter:description" content="Discover articles organized by series in this site index." />
{% endblock meta_description %}
{% block content %}
{% block header %}
{{ super() }}
{% endblock header %}
<h1>Series</h1>
{% if terms %}
<ul>
{% for term in terms %}
<li>
<a href="{{ term.permalink | safe }}">{{ term.name }}</a>({{ term.pages | length }})
</li>
{% endfor %}
</ul>
{% endif %}
{% endblock content %}

View File

@@ -0,0 +1,23 @@
{% extends "index.html" %}
{% block title %}Posts in "{{ term.name }}"{% endblock title %}
{% block og_title %}Posts in "{{ term.name }}"{% endblock og_title %}
{% block twitter_title %}Posts in "{{ term.name }}"{% endblock twitter_title %}
{% block meta_description %}
<meta name="description" content="Discover articles from the '{{ term.name }}' series in this site index." />
<meta property="og:description" content="Discover articles from the '{{ term.name }}' series in this site index." />
<meta property="twitter:description" content="Discover articles from the '{{ term.name }}' series in this site index." />
{% endblock meta_description %}
{% block content %}
{% block header %}
{{ super() }}
{% endblock header %}
<h1>{{ term.name }} - Series</h1>
{% for page in term.pages %}
{{ post_macros::page_in_list(page=page) }}
{% endfor %}
{% endblock content %}

View File

@@ -13,6 +13,7 @@ taxonomies = [
# You can enable/disable RSS # You can enable/disable RSS
{ name = "categories", feed = true }, { name = "categories", feed = true },
{ name = "tags", feed = true }, { name = "tags", feed = true },
{ name = "series", feed = true },
] ]
author = "Leon" author = "Leon"
@@ -37,6 +38,7 @@ taxonomies = [
# You can enable/disable RSS # You can enable/disable RSS
{ name = "categories", feed = true }, { name = "categories", feed = true },
{ name = "tags", feed = true }, { name = "tags", feed = true },
{ name = "series", feed = true },
] ]
radion_title = "Leon's Personal Blog" radion_title = "Leon's Personal Blog"
@@ -44,6 +46,7 @@ radion_menu = [
{ url = "$BASE_URL", name = "Home" }, { url = "$BASE_URL", name = "Home" },
{ url = "$BASE_URL/categories", name = "Categories" }, { url = "$BASE_URL/categories", name = "Categories" },
{ url = "$BASE_URL/tags", name = "Tags" }, { url = "$BASE_URL/tags", name = "Tags" },
{ url = "$BASE_URL/series", name = "Series" },
] ]
theme = "toggle" # options: {light, dark, auto, toggle} theme = "toggle" # options: {light, dark, auto, toggle}
toc = true toc = true