• Pl chevron_right

      Aryan Kaushik: Open Forms is now 0.4.0 - and the GUI Builder is here

      news.movim.eu / PlanetGnome • 1 month from now • 3 minutes

    Open Forms is now 0.4.0 - and the GUI Builder is here

    A quick recap for the newcomers

    Ever been to a conference where you set up a booth or tried to collect quick feedback and experienced the joy of:

    • Captive portal logout
    • Timeouts
    • Flaky Wi-Fi drivers on Linux devices
    • Poor bandwidth or dead zones

    Meme showcasing wifi fails when using forms

    This is exactly what happened while setting up a booth at GUADEC. The Wi-Fi on the Linux tablet worked, we logged into the captive portal, the chip failed, Wi-Fi gone. Restart. Repeat.

    Meme showing a person giving their child a book on 'Wifi drivers on linux' as something to cry about

    We eventually worked around it with a phone hotspot, but that locked the phone to the booth. A one-off inconvenience? Maybe. But at any conference, summit, or community event, at least one of these happens reliably.

    So I looked for a native, offline form collection tool. Nothing existed without a web dependency. So I built one.

    Open Forms is a native GNOME app that collects form inputs locally, stores responses in CSV, works completely offline, and never touches an external service. Your data stays on your device. Full stop.

    Open Forms pages

    What's new in 0.4.0 - the GUI Form Builder

    The original version shipped with one acknowledged limitation: you had to write JSON configs by hand to define your forms.

    Now, I know what you're thinking. "Writing JSON to set up a form? That's totally normal and not at all a terrible first impression for non-technical users." And you'd be completely wrong, to me it was normal and then my sis had this to say "who even thought JSON for such a basic thing is a good idea, who'd even write one" which was true. I knew it and hence it was always on the roadmap to fix, which 0.4.0 finally fixes.

    Open Forms now ships a full visual form builder.

    Design a form entirely from the UI - add fields, set labels, reorder things, tweak options, and hit Save. That's it. The builder writes a standard JSON config to disk, same schema as always, so nothing downstream changes.

    It also works as an editor. Open an existing config, click Edit, and the whole form loads up ready to tweak. Save goes back to the original file. No more JSON editing required.

    Open forms builder page

    Libadwaita is genuinely great

    The builder needed to work well on both a regular desktop and a Linux phone without me maintaining two separate layouts or sprinkling breakpoints everywhere. Libadwaita just... handles that.

    The result is that Open Forms feels native on GNOME and equally at home on a Linux phone, and I genuinely didn't have to think hard about either. That's the kind of toolkit win that's hard to overstate when you're building something solo over weekends.


    The JSON schema is unchanged

    If you already have configs, they work exactly as before. The builder is purely additive, it reads and writes the same format. If you like editing JSON directly, nothing stops you. I'm not going to judge, but my sister might.

    Also thanks to Felipe and all others who gave great ideas about increasing maintainability. JSON might become a technical debt in future, and I appreciate the insights about the same. Let's see how it goes.

    Install

    Snap Store

    snap install open-forms
    

    Flatpak / Build from source

    See the GitHub repository for build instructions. There is also a Flatpak release available .

    What's next

    • A11y improvements
    • Maybe and just maybe an optional sync feature
    • Hosting on Flathub - if you've been through that process and have advice, please reach out

    Open Forms is still a small, focused project doing one thing. If you've ever dealt with Wi-Fi pain while collecting data at an event, give it a try. Bug reports, feature requests, and feedback are all very welcome.

    And if you find it useful - a star on GitHub goes a long way for a solo project. 🙂

    Open Forms on GitHub

    • Pl chevron_right

      Benjamin Otte: Snapping

      news.movim.eu / PlanetGnome • 1:41 • 1 minute

    With the release of 4.23.1, GTK’s renderer will come with a new feature that we’ve called snapping .

    How does it work?

    Snapping is enabled by calling gtk_snapshot_set_snap() . If enabled, it will slightly adjust the placement of rectangles when drawing so that they align with the pixel grid and don’t cover half a pixel.

    GSK_RECT_SNAP_ROUND

    Content drawn with GTK is scaled automatically by the desktop’s scale factor. But with the arrival of native fractional scaling, it is no longer possible to know if content is aligned to the pixel grid.

    While that is usually not a problem, there are a few cases where it is:

    Sprite grids

    Gameeky is a learning game that plays on a grid. Unfortunately, on a fractionally scaled machine, it can end up looking like this:

    A screenshot of a Gameeky game without snapping. A distracting grid can be seen between the sprites.

    Once those sprites are snapped to the pixel grid by rounding to the nearest pixel, the same image looks like this
    A screenshot of a Gameeky game with snapping. No grid is visible anymore and it looks like a single image.

    Sharp images

    Often Applications want to display images in a way that matches the pixels of the image 1:1 with pixels of the monitor. This is a challenge on a fractionally scaled display. Not only is it important to get the scale factor right, it’s also important to align the pixels correctly, or they will appear slightly blurry.

    The use case is not just image viewers that want to offer a 1:1 zoom factor, but all applications that redirect drawing, from game emulators to viewers like Boxes or Connections .

    Hardware optimizations

    And finally, there are optimizations like graphics offload that rely on content being aligned to the pixel grid or the hardware cannot optimize them. So it is important to snap content to the pixel grid for those cases.

    Why don’t we just always snap to the grid?

    There is one big problem with automatic snapping: smoothness. Because snapping only works on full pixels, doing slow animations causes content to jump from one pixel to the next. And that causes jitter.

    The main situation where one can see this is smooth scrolling, like in this example:

    Summary

    The next GTK release will offer a new way to tame the effects of fractional scaling.  Please try it out and let us know how it works!

    • Pl chevron_right

      Michael Calabrese: Synchronizing Timeline Ticks with GES Framerates in Rust

      news.movim.eu / PlanetGnome • 1 day ago • 1 minute

    While working on my GSoC project (rewriting the Pitivi timeline in Rust), I ran into an issue getting precise UI ticks that map to the absolute nanosecond timestamps of the video frames. Initially I hardcoded NTSC fractional math (24000/1001) to calculate the boundaries of frames.

    This led to issues with truncated timestamps, and had a glaring issue with other framerates (like 30fps). I needed a more robust solution that could handle any framerate and provide accurate tick positions.

    I assumed that I could extract the framerate directly from ges::Timeline , however there is no direct getter in the Rust bindings. After some digging, I discovered that the framerate is actually stored in the gst::Caps of the timeline's video stream as a gst::Fraction .

    My Approach

    The steps I used:

    • Enumerate the timeline tracks ( timeline.tracks() )
    • Filter for the video track (checking ges::TrackType::VIDEO )
    • Read the track's restriction_caps
    • Extract the gst::Fraction

    My helper

    I wrote a helper function to extract the framerate from the timeline:

    
    pub fn get_fps(&self, timeline: &ges::Timeline) -> Option<(i128, i128)> {
        timeline
            .tracks()
            .into_iter()
            .find(|track| track.track_type().contains(ges::TrackType::VIDEO))
            .and_then(|track| {
                let caps = track.restriction_caps().or_else(|| track.caps())?;
                let structure = caps.structure(0)?;
                let fps = structure.get::<gst::Fraction>("framerate").ok()?;
    
                // Extract the safe numerator and denominator
                Some((fps.numer() as i128, fps.denom() as i128))
            })
    }
    
    // ... inside the timeline injection logic:
    if let Some((fps_num, fps_denom)) = self.get_fps(timeline) {
        self.fps_num.set(fps_num.max(1) as i32);
        self.fps_denom.set(fps_denom.max(1) as i32);
    } else {
        // Default to 23.976 if we can't find a valid framerate caps
        self.fps_num.set(24_000);
        self.fps_denom.set(1_001);
    }
    
    

    I make some assumptions here, such as only one video track existing, and that the framerate is always present in the caps. This solution made the tick spacing and labels line up with the timeline’s actual frame boundaries at any framerate.

    • Pl chevron_right

      Nick Richards: Fuzzy Time Everywhere

      news.movim.eu / PlanetGnome • 2 days ago • 2 minutes

    I do not always want to know what time it is. This is a slightly awkward position for someone who keeps making clocks, but there we are. Quite often the useful answer is not 17:42 . It is “quarter to six”, “nearly lunch” or “you should probably start thinking about leaving”. The precise time is useful when catching trains, baking things and joining calls; the rest of the time it can be a bit much.

    So I have been working on fuzzy time for a while. The first version I made was for the Pebble , which remains one of those devices that makes later technology feel slightly ashamed of itself. A small always-on screen, good battery life, physical buttons and just enough personality. It’s not tokyoflash after all.

    The current versions are Fuzzy Time GB , a Wear OS watch face, and Fuzzy Clock GB , a GNOME Shell extension.

    Fuzzy Time GB watch face showing a fuzzy time phrase

    The Android version is quite a funny object internally. It is a Watch Face Format v2 face, so the APK has no app code:

    android:hasCode="false"
    

    The face itself is declarative XML. Since writing thirty-six thousand lines of watch face XML by hand would be a cry for help, there is a generator which writes the cases out from the same fuzzy time rules. For every hour and every five-minute bucket it emits the condition, text and separate interactive and ambient versions.

    That sounds excessive until you look at the details; and then it still sounds excessive. There are lots of pernickety things that give this the correct GB locale to my ears. “Five Past Midnight” is a real phrase. 23:58 should say “Midnight”, and if the date is visible it should be tomorrow’s date. 11:58 should say “Noon”. “O’Clock” wants different spacing and weight from “Twenty-five To”. Ambient mode wants smaller, quieter text. A round watch face leaves less room than you think it does. The watch face has a few small choices rather than a settings cathedral: warm white, cool white, soft green, dim amber; system font or Arvo ; optional radial complication slots above and below the text. The range complications are deliberately arcs around the edge rather than little widgets in the middle. They can show useful things, but they should not make the face stop being mostly words and calm black space.

    Fuzzy Time GB GNOME Shell extension

    The GNOME version is the same idea on a different surface. It finds the existing clock label, listens to the same wall clock, respects the existing “show date” and “show weekday” settings, and changes the text. I have wanted to build something like this for years, partly because of Emmanuele Bassi’s word clock extension . That extension was great, but not quite the thing I wanted , so eventually I got around to making my own.

    One of the few design decisions left that I helped on in main GNOME (which is much better now) is that the shutdown and logout dialogue only updates its timing every so often. It could update every second; the computer is quite capable of counting. But it’s much more pleasant when the number doesn’t twitch constantly while you are trying to decide whether you meant to press the button.

    You can build both projects from source. I may choose to distribute them in a more structured fashion in future. The Android one is a minimal Wear OS watch face, and the GNOME one is a normal Shell extension that currently supports GNOME Shell 45 to 50.

    • Pl chevron_right

      Shivam: Journey Starts : Gitg Port to GTK4

      news.movim.eu / PlanetGnome • 3 days ago • 1 minute

    About Me

    Hello Everyone! I am Shivam, I am currently pursing my engineering in Electronics. I have been selected for GSoC 2026 for the port of GNOME-Gitg from GTK 3 to GTK 4. I am starting this blog in order to document my journey of porting Gitg . I have been contributing in GNOME from several months and in awe with the supportive and helpful nature of the community.

    Project

    As many of you probably know, Gitg is still using GTK3, which means it misses out on a lot of the improvements and features that came with GTK4. The main goal of this project is to port Gitg from GTK3 to GTK4 and then gradually modernize the application.

    The scope of the project itself is quite large, and that’s honestly one of the most exciting parts for me. Working on this port will help me understand the application interacts with different libraries and components behind the scenes.

    At the same time, I hope that this work will help the new contributors like me easier to get started contributing to the various GNOME projects 🙂

    Conclusive Goal

    The final goal of this project is to get Gitg building and running completely with GTK4 dependencies. At the moment, the application still fails to compile, which is expected since many GTK3 APIs are still present throughout the codebase.A separate GTK4 branch already exists where parts of the migration work have been started, and several components have already been adapted to GTK4. This project will continue building on top of that existing effort and gradually move the remaining parts of the application to the newer toolkit.

    I would also like to sincerely thank the contributor(s) who have worked on the GTK4 porting work earlier. Their efforts created the foundation for this project, and I’ll be continuing from the work they have already done.

    Thank You For Reading!

    PS:- I would also like to thank Alberto Fanjul for mentoring me in this project and Felipe Borges for this time and support.

    • Pl chevron_right

      Christian Hergert: ((lib)Re)bonjour

      news.movim.eu / PlanetGnome • 5 days ago • 4 minutes

    I made another weird side project while unemployed. In fact I’ve wanted it for a while but once I learned that “Rebonjour” is the word for “hello again” I just had to finish the library.

    librebonjour is an asynchronous DNS-SD and mDNS client library for GLib applications. Or, more practically, it is a small GObject API over the two local service-discovery providers you are likely to find on a Linux system: Avahi and systemd-resolved.

    It does not link against either of them. It only talks to them over D-Bus.

    The reason for that is mostly boring, which is usually where the useful things are. Applications should not need to care if a machine has Avahi running, or if it is using systemd-resolved for mDNS. They should be able to discover a service, resolve it, maybe advertise something, and get on with whatever they were actually trying to do.

    So RebonjourClient selects a backend internally. If org.freedesktop.Avahi is available on the system bus, it uses Avahi. If not, it falls back to systemd-resolved’s org.freedesktop.resolve1 API. If neither is around, availability checks fail like you would expect.

    The public API stays the same either way.

    What It Does

    There are three common things I wanted to make pleasant.

    First, one-shot discovery. Ask for the service types in local , ask for instances of something like _ipp._tcp , then resolve one of those instances into addresses and TXT metadata.

    Second, browser-style discovery. A RebonjourBrowser owns a stable GListModel of RebonjourService objects. That fits nicely into GTK code because the model object can stay the same while the contents change underneath it.

    Third, registration. You can describe a local service with RebonjourServiceDescription , register it, and keep the returned RebonjourRegistration alive for as long as the service should be advertised.

    Resolving a service gives you a RebonjourResolvedService . That contains the SRV result, TXT data, priority, weight, and a model of RebonjourEndpoint objects. The endpoints hold the GSocketAddress you would actually use to connect.

    Why Two Backends

    Avahi is the nicer backend for browsing. Its D-Bus API gives you long-lived browser objects and emits signals when services appear and disappear. That maps very naturally to GListModel changes.

    systemd-resolved is different. It has useful DNS-SD and mDNS operations over D-Bus, but the browsing side is lookup-based. That means you can ask what is there, but you do not get the same live add/remove signal stream that Avahi provides.

    I did not want applications to have to care about that distinction unless they really want to. So the browser has auto-refresh and refresh-interval properties. With Avahi, auto-refresh is effectively harmless because the model is already live. With systemd-resolved, it starts an internal refresh loop and updates the model for you.

    It is not magic. It is just putting the backend-shaped unpleasantness in one place so application code can stay boring.

    Asynchronous with libdex

    The whole thing is built on libdex . Anything that might touch D-Bus or the network returns a DexFuture .

    That means construction, availability checks, service-type lookup, instance lookup, resolving, registration, browser refresh, and unregistering are all future-based. If you are already writing fiber-style code with libdex, the API fits into that directly:

    g_autoptr(RebonjourClient) client = NULL;
    g_autoptr(GListModel) services = NULL;
    g_autoptr(GError) error = NULL;
    
    if (!(client = dex_await_object (rebonjour_client_new (), &error)))
      g_error ("%s", error->message);
    
    services = dex_await_object (rebonjour_client_lookup_instances (client,
                                                                    0,
                                                                    "_ipp._tcp",
                                                                    NULL,
                                                                    REBONJOUR_LOOKUP_FLAGS_NONE),
                                 &error);
    

    The 0 there means any interface. Passing NULL for the domain uses local . The common case should not require looking up interface indexes which I’m pretty sure most people reading this have never even done before.

    Advertising

    Advertising is where things get more system-policy-oriented.

    With Avahi, registration goes through Avahi’s D-Bus API. With systemd-resolved, registration uses RegisterService and UnregisterService , which are polkit-protected. Also, resolved needs full mDNS enabled with MulticastDNS=yes ; MulticastDNS=resolve is enough to browse and resolve, but not enough to respond as a service.

    So librebonjour can expose one API for registration, but it cannot make host policy disappear. Applications still need to handle authorization failure, missing mDNS responder support, sandbox boundaries, or whatever policy the system administrator has decided is appropriate.

    That seems like the right way to demarcate things. The library should hide the provider mechanics, not the permissions of the platform.

    Why

    Mostly because I wanted this to exist.

    DNS-SD is handy. Local-network service discovery is still useful. But using it from a GLib application means either caring too much about the provider or writing just enough glue that every application gets to have its own slightly different version of the same code.

    And even worse is having to bundle things to build projects like Avahi for Flatpak when you only use the library which calls into D-Bus anyway.

    This is not a grand platform initiative. It is not something I am employed to maintain. So you know, use wisely.

    • Pl chevron_right

      Michael Meeks: 2026-05-22 Friday

      news.movim.eu / PlanetGnome • 6 days ago

    • Early interview & job offer for a support role, catch up with Anna, sync with Dave, catch up with a partner, 1:1 with Naomi.
    • Sync with Thorsten, Skyler, Pedro, poked at slides.
    • Dave, Emily, Jake, Pete & Eli arrived - played in the garden somewhat, enjoyed company, tried to cook a meal; caught up happily after a decade+ gap. Bed early.
    • Pl chevron_right

      This Week in GNOME: #250 Sideloading

      news.movim.eu / PlanetGnome • 6 days ago • 5 minutes

    Update on what happened across the GNOME project in the week from May 15 to May 22.

    Third Party Projects

    Alexander Vanhee reports

    Last Saturday, Bazaar was updated to 0.8.0 with the ability to install .flatpak bundles. We created a fancy new dialog so people can better understand what happens when they install one. We also added the ability to remove app caches directly from within the sizes dialog and reworked the app install animations.

    Feel free to leave your feedback on the GitHub repo !

    bazaar_bundle.BKRpfvv5_cHJ1M.webp

    bazaar_cache.BfwwHb7S_Z2iEV8y.webp

    Luiggi R. Cardoso says

    Draft v1.3 has been released!

    You know those text snippets you need to save, that quick idea you want to write down, or a link you need to hold onto but don’t want to open a heavy app for? This is Draft.

    This new version brings:

    • Estonian and Brazilian Portuguese Translations (thanks to our community!).
    • Keyboard shortcuts for formatting options.
    • Under-the-hood performance fixes and minimal spellchecking support.

    Download it on Flathub | Contribute on GitLab | Help with Translations

    draft-screenshot.BUjBAXZH_ZJArlS.webp

    Bilal Elmoussaoui reports

    I have released a new version of gobject-linter . The release includes:

    • Parse custom types using g_type_register_static directly
    • Add a new unused vfuncs rule
    • Add a new missing_g_begin_decls rule
    • Generate fixes for g_object_virtual_methods_chain_up , missing_autoptr_cleanup
    • gi_missing_since rule now validates that enum members don’t include inlined Since: annotations
    • Improvements to various existing rules

    francescocaracciolo says

    Newelle 1.4.0 Released! Newelle (AI Assistant and Agent for Gnome) has received a new major update!

    🔗 Added Interfaces: alternative way external applications can interact with Newelle! Featuring Telegram support, APIs, WebUI and more!

    👷 Support for directly download pre-compiled binaries for llama.cpp instead of compiling

    🔐 Better Command permissions

    💬 Better Prompt Editing

    📝 Better Font rendering and customization

    Get it on Flathub: https://flathub.org/it/apps/io.github.qwersyk.Newelle

    Wladimir Palant reports

    Gnome Commander 2.0 has been released! Many changes:

    • Rewritten in Rust, with better performance and stability.
    • Added embedded terminal to display output of commands run in Gnome Commander.
    • Redesigned Quick Search can now be used to filter the file list as well.
    • Far better search performance and many more improvements to the Search dialog.
    • Consistent handling of file encodings in the internal viewer along with lots of other improvements.
    • Improved accessibility of the application, screen readers should be able to recognize the context everywhere now.
    • More tab state is being restored on restart – selected file, column sizes and ordering, hidden columns.
    • Tons of fixed bugs and more .

    commander.DZi49Kc3_Z1cLSkU.webp

    Anil reports

    Codd 0.5.0 has been released!

    Since the first Flathub release, several new features and improvements have been added:

    • Secure password storage using the system keyring
    • SQL script generation for tables
    • A Table Inspector for inspecting columns, data types, defaults, constraints, indexes, foreign keys and triggers
    • Additional table action options

    Spanish translations have also been added recently, thanks to fvtronics.

    Codd is a lightweight PostgreSQL client for GNOME, available on Flathub. Feedback, bug reports, and feature ideas from PostgreSQL users are very welcome.

    Get it on Flathub or check out the source code .

    codd_1.l0YU6Bk2_10frGv.webp

    codd_2.0NemD-O__1rTnKN.webp

    Gitte

    A simple Git GUI for GNOME

    Christian announces

    🎉 Gitte 0.4.0 released!

    Gitte is a GTK4/libadwaita Git interface for the GNOME desktop, written in Rust.

    This version introduces a mainline concept: you can now mark a branch as your project’s mainline and use it as a reference for merged-checks, filtering, and a brand-new “Sync with mainline” action that rebases your branch and fast-forwards it in one step. The commit log also gained a filter to only show commits not yet in the mainline.

    Working with changes got more flexible: you can now revert commits straight from Gitte, partially stage untracked files, toggle an additive selection mode in the changed files list, and ignore whitespace in diffs. Staging and unstaging are now available via context menu, Enter, and double-click, and you can create a branch directly from any commit in the log.

    The UI received a lot of polish. The push and pull dialogs are now visually distinguishable and show their targets in tooltips, “ahead/behind” is rendered as text instead of arrows, popovers no longer claim unnecessary width, and several diff styling glitches are gone. Checking out a branch now creates a detached HEAD, with switching as a separate, explicit action.

    This release also brings a long list of fixes around macOS shutdown and keyboard shortcuts, keyboard navigation, files without trailing newlines, now respects fetch.prune = true in the pull dialog, and various small papercuts.

    Get it on Flathub , for macOS or have a look at the Code .

    gitte_1.DyzkWrbO_2d1LFM.webp

    gitte_2.C8oexdPP_Zpbqph.webp

    gitte_3.D5Ve8sB2_Jtaev.webp

    Fractal

    Matrix messaging app for GNOME written in Rust.

    Kévin Commaille reports

    Here comes Fractal 14.rc. This release candidate comes with a fair amount of quality of life improvements:

    • The sidebar room filter has been improved: Enter goes to first room result, and there’s an empty state when no results match the term.
    • The performance of the room list has also been improved, it should be mostly noticeable for accounts that have joined a lot of rooms.
    • Informative events ( Unable to decrypt , server notices…) are now styled differently to reflect their special nature and differentiate them from regular text messages that anyone can send.
    • Calls are rendered in the timeline and incoming calls trigger a notification. We still don’t support calls, but at least now you know when someone is calling and can open another client to answer.

    As usual, this release includes other improvements, fixes and new translations thanks to all our contributors, and our upstream projects.

    It is available to install via Flathub Beta, see the instructions in our README .

    As the version implies, it should be mostly stable and we expect to only include minor improvements until the release of Fractal 14.

    If you want to join the fun, you can try to fix one of our newcomers issues . We are always looking for new contributors!

    fractal-utds-and-calls.BUaXhMyF_1WUEfz.webp

    Miscellaneous

    Damned Lies

    The internal application to manage localization of GNOME & friends modules

    Guillaume Bernard says

    GNOME Damned Lies has received a few improvements this week! Focusing on the issues identified for the next GNOME 51 release, we implemented the auto-closing feature for translations pushed through a merge request. This way, a background job regularly checks the status of the merge request (GitLab instances and Github.com are supported) to auto-close workflows.

    We also updated the look and feel of the vertimus workflows to use more native Boostrap base style and removed the custom CSS that was used to render the action history, easing the maintenance.

    In addition to that, we added support for Codeberg.org projects. Only direct pushes are supported at this time, because we need to implement the plugin to open/check merge requests on Forgejo software. We are on our way to also support Freedesktop’s GitLab instance soon.

    That’s all for this week!

    See you next week, and be sure to stop by #thisweek:gnome.org with updates on your own projects!

    • Pl chevron_right

      Thibault Martin: I realized that A cheap VPS is a good front

      news.movim.eu / PlanetGnome • 6 days ago • 1 minute

    I have a server at home. It runs a Kubernetes cluster and a few services. I want to expose them to the Internet, so I can e.g. share public links from my Nextcloud, or synchronize my Kobo reader with Grimmory . But I don't want to expose my home IP to the world, and I want to have some reasonable protection against unsophisticated DoS attacks.

    I realized that I can achieve that with a cheap VPS that acts as a front, HAProxy , and Wireguard .

    I rented a tiny VPS for €4/month at Infrawire (1 vCPU, 2 GB RAM, 25 GB NVMe). I installed a Debian 13 on it, because I want that front server to be as stable and low maintenance as possible, and installed the Debian-packaged HAProxy onto it. I also installed Wireguard. The VPS has a publicly accessible IP, so it will be my Wireguard server: my server at home can reach the VPS to establish a tunnel, the opposite is not true.

    On my k3s node, I've installed Wireguard as well. I configured Wireguard on the VPS and my k3s node to establish a tunnel between the two. I've also bound the sshd on my VPS to the wireguard address. Infrawire offers a console so I can unstick myself if I locked me out of my own server (e.g. by misconfiguring Wireguard on any side, or if my server at home had any failure).

    I pointed all my DNS records to the VPS. The HAProxy is a "dumb" tcp forwarder, so I can keep operating like before on my cluster. In particular, HAProxy doesn't do TLS termination. My certificates are fetched on my cluster by cert-manager like before, using the http-01 challenge and Let's Encrypt. I could also move to dns-01 challenges, but http-01 just works and lets me switch to a registrar without an API if need be.

    That way, I don't need a fixed IP at home, and I don't have to do any port-forwarding from my home router to my k3s cluster. Even better: the VPS has an anti-DDoS protection included, and I can also configure HAProxy to refuse too many connections from a same IP, I can make it close TCP connections that take too long to establish, and more. If my VPS gets hammered, I can still access my services from within my home network.