Kamis, 05 November 2026

FOSS Weekly #26.45: Fedora 45, Immutable Ubuntu, Mint New Feature, Dual Boot Without USB and More Linux Stuff

FOSS Weekly

Fedora 45 arrived about two weeks late, but it’s packed. You get Linux 7.2, GNOME 51, and Plasma 6.7, plus kmscon replacing the old text console and a QR code that now leads to a page for kernel panics.

Could Ubuntu go immutable? Canonical has restarted Ubuntu Core Desktop, a snap-based system that stays locked down, and a dedicated team is now working on it. Both it and a proposed LTS firmware package are still early.

Ubuntu and many other distros turned hibernation off by default years ago, partly because it was slow. Linux Mint is bringing it back, with a few useful tweaks..

KDE Linux will load the ntsync kernel module by default. It gives Wine and Proton a closer match to Windows sync primitives, which can substantially boost performance.

A ministry merger ended Denmark’s Collabora pilot before its evaluation, Collabora says. It adds that the result shouldn’t count against the suite, and it says the first phase worked well.

France’s National Assembly has backed a tax of 5.5 euro cents per gigabyte delivered to French internet subscribers. VideoLAN, the nonprofit behind VLC, says it could cost over €1 million a year.

Nitrux is warning users that nitrux.org is an unaffiliated, AI-generated site using its name and logo.

CTA Image

Serious about using Ubuntu Linux on servers? Mastering Ubuntu Server book has now been updated for Ubuntu 26.04 LTS. The new version is available on Amazon.

This bestseller book is written by Jay LaCroix, of LearnLinuxTV and Ken VanDine, Staff Engineer at Ubuntu. From the Linux fundamentals to Ansible, MicroK8s and even Local AI with Lemonade, the book covers it all.

Mastering Ubuntu Server

🧠 What We’re Thinking About

There are two trends I am noticing. More European countries are opting for open source software in public institutions.

Latest in this list is the news that schools in six German states can now sign up for a Nextcloud-based cloud run by IONOS in German data centers. The four-year contract is open to other states later, and schools aren’t required to join.

The next trend is to use AI for creating open source alternatives to mainstream software as a service platforms like Adobe and Microsoft. Take the example of Puter’s new everything repo that offers more than 50 open source web apps, under MIT, that you can use in the browser or self-host.

🧮 Linux Tips, Tutorials, and Learnings

You don’t need a full VPN app for occasional use on Linux. Vivaldi with Proton VPN, Firefox’s built-in VPN, and Opera all offer free browser-only options.

The mv command has an interesting new option that lets you swap two files or directories. No need for the usual 3 steps with temp file in between.

Here's a collection of projects that let you run Linux directly on Apple Silicon Macs. My pick would be Fedora Asahi Remix, which works best, with nearly everything functional on M1 and M2 machines, while M3 support is still new.

👷 Homelab and Hardware Corner

You can give every container in your homelab its own name instead of an IP and port by using Tailscale Services.

In other news, Raspberry Pi has yet again raised prices, this time on its 16GB Raspberry Pi 5, which goes up $80 to $385. The 16GB Compute Module 5 also sees a $40 hike.

✨ Apps and Projects Highlights

Remember Wubi? Libertix brings that idea back, letting you install Linux Mint or Zorin from inside Windows without a USB drive.

📽️ Videos for You

Kitty is awesome. Here is a video that shows the tweaks to make it even more awesome.

💡 Quick Handy Tip

firefox quick tip

The "Hide tabs and sidebar" option on Firefox merges the top tab bar into a slim vertical ribbon along your screen's edge. Hiding the top tabs frees up valuable vertical space, expanding your main view while keeping any open tabs reachable via a quick hover.

To turn it on, right-click the top toolbar, pick "Customize Toolbar," and drop the Sidebar button onto your bar. Click the new icon, right-click inside the sidebar, choose "Customize Sidebar," and enable the "Hide tabs and sidebar" option to collapse everything instantly.

🎋 Fun in the FOSSverse

Our Shortform Showdown puzzle will have you identify a series of short forms or abbreviations for commonly used Linux tools, commands, and concepts.

GRUB is King.

📆 Trivia Bit: In 1952, CBS used UNIVAC to forecast the presidential race. Its early call gave Eisenhower 438 electoral votes to Adlai Stevenson’s 93, but the team held it back because the result looked implausible. Eisenhower ultimately won 442 to 89.

🧑‍🤝‍🧑 From the Community: The thread by Pro FOSSer Ernest about who uses AI and what one understands about it is still going strong. Do you have anything to add to the conversation?



from It's FOSS https://ift.tt/QwrsOtR
via IFTTT

This Overlooked Linux mv Option Replaces Three Commands With One

mv exchange

Every Linux user has done the three-step dance at least once. You want to swap two files, so you move the first one to a temporary name, move the second one into its place, and then move the temporary file back.

Ever felt that this is a bit... clumsy? And for a moment, one of your files simply doesn't exist. That doesn't feel right!

Since version 9.5, GNU coreutils has a better way: mv --exchange.

In this article, I'll show you what it does, how to check if your system has it, where it actually helps, and the mistake I made the first time I used it on directories.

The old way: three moves and a temporary name

Let's say you have two configuration files, nginx.conf and nginx.conf.new, and you want the new one to take the place of the old one while keeping the old one around.

The classic approach looks like this with the mv command:

mv nginx.conf nginx.conf.tmp
mv nginx.conf.new nginx.conf
mv nginx.conf.tmp nginx.conf.new

There are two problems here:

  • There is a gap. Between the first and second command, nginx.conf doesn't exist. If a service reads it at that exact moment, it finds nothing (and trust me, one day that will happen).
  • It can fail halfway. If the second command fails, you are left with a temporary file and a half-finished swap to clean up by hand.

The exchange option does this in a single command:

mv --exchange nginx.conf nginx.conf.new

Swap files with mv --exchange

The --exchange option tells mv to swap the two paths in a single operation. On Linux, it uses the kernel's renameat2() system call with the RENAME_EXCHANGE flag, so the swap is atomic: at any moment, both names exist and point to a file.

Check if your mv supports it

First, check your coreutils version:

mv --version

On my machine, running NixOS, I get:

mv (GNU coreutils) 9.11

You need version 9.5 or newer. Careful here: version numbers are not decimals. They are compared part by part, so 9.11 (nine point eleven) is newer than 9.5. You can also check whether the option is listed in the help:

mv --help | grep exchange

If this prints nothing, your mv doesn't have the option yet. I'll talk about which distributions ship it later in this article.

Swap two files

Let's create two small files to test:

echo "config A" > a.conf
echo "config B" > b.conf

Now swap them:

mv --exchange a.conf b.conf

Check the result:

cat a.conf b.conf
config B
config A

That's it. The contents traded places in one command, and no temporary file was involved.

Swapping two files with mv --exchange in the terminal

Swap two directories (and the mistake I made)

It works for directories too, which is where it really shines. But the first time I tried it, it failed:

mkdir release-old release-new
touch release-old/v1 release-new/v2
mv --exchange release-old release-new
mv: cannot exchange 'release-old' and 'release-new/release-old': Unknown error -1

Look at the second path in the error: release-new/release-old. When the last argument is an existing directory, mv assumes you want to move things into it, just like a regular mv. So it tried to swap release-old with a path inside release-new that doesn't exist.

The fix is the -T (--no-target-directory) option, which tells mv to treat the last argument as a plain name instead of a destination folder:

mv -T --exchange release-old release-new
ls release-old release-new
release-new:
v1

release-old:
v2

Now the whole directory trees were swapped, no matter how many files they contain, because only the names changed.

Where it actually helps

Here are a few situations where I find mv --exchange useful:

  • Rolling out a new configuration. Swap the new config into place and keep the old one under the other name, ready for a quick rollback with the same command.
  • Switching release directories. If you deploy a new version of a site into a separate folder, you can swap it with the live folder in one step (remember the -T).
  • Undoing a swap. Running the same command again swaps the files back.

Limitations you should know

mv --exchange is not magic, and a few things can trip you up.

Both paths must be on the same filesystem

The swap only works when both files live on the same filesystem. If you try to exchange a file on one filesystem with a file in a tmpfs mount like /dev/shm, the kernel refuses the operation:

mv --exchange b.conf /dev/shm/x.conf
mv: cannot exchange 'b.conf' and '/dev/shm/x.conf': Unknown error -1

The message is not very helpful (it is the same "Unknown error -1" we saw in the directory mistake above), so it's worth knowing the real reason: the kernel can't swap names across two different filesystems.

Regular mv gets around this by copying the file and deleting the original, but that can't be done atomically, so --exchange doesn't even try.

mv --exchange failing when the files are on different filesystems

The filesystem must support it

The underlying system call needs filesystem support. Common Linux filesystems such as ext4, Btrfs, XFS and tmpfs support it.

Conclusion

mv --exchange is one of those small additions that removes a whole category of fiddly, error-prone shell work. If you are on a recent distribution like NixOS, Fedora or Debian 13, it is already on your system.

Next time you reach for a temporary file name to swap two things, try mv --exchange instead. Just remember to add -T when you swap directories.

✍️
Igor Giamoniano is a developer and teacher from São Paulo, Brazil. He contributes to the Brazilian Portuguese translation of the Linux kernel documentation and runs Coisa de Dev, where he teaches Linux and programming to a Brazilian audience. You can find his open source work on GitHub.


from It's FOSS https://ift.tt/3XKrNFl
via IFTTT

Systemd Wants Rust in, But its Core Will Have to Wait

systemd rust banner

systemd, the init system and service manager that most mainstream Linux distributions use, has merged support for writing its programs in Rust. It landed on the main branch through a pull request of 19 commits.

The pull request replaces an earlier proposal and follows rules the maintainers agreed upon at a recent hackfest in Berlin.

The first program to use it is systemd-keyring-setup, and it adds X.509 certificates to kernel keyrings, such as the dm-verity and fs-verity ones, at boot before locking them down.

If you remember, we recently talked about Amutable launching a preview for their new offering. Anyhow, let's see what they are up to now.

Rust, but on systemd's terms

With the approach they are following, Rust stays optional, and systemd can still be built without it.

The Rust code's scope is limited to new standalone programs that nothing else depends on, while everything builds with meson, excluding cargo from the equation. Only the core and libc crates are allowed, and that rules out the standard library and alloc.

Programs would use systemd's own Box and Vec types, and a failed allocation would return an error instead of panicking. A Rust program must also behave like its C counterparts, with a test checking this for programs that declare the same command.

Building Rust programs involves having rustc 1.85, bindgen 0.71.1, and meson 1.4.0 or newer.

Distributions that build systemd with every feature enabled, such as Arch and openSUSE OBS, will have to install the Rust toolchain or turn Rust support off.

The shared library problem

The library that systemd's programs share, libsystemd-shared, stays in C for now. Putting Rust in it would need proper shared library support from Rust itself, and the language still has no stable ABI.

Rust programs use that C library the same way C programs do, and putting Rust inside it would duplicate the same code across the library and many of systemd’s binaries.

Filesystem access is another hurdle, since systemd looks up paths through file descriptors to keep that step secure and does not accept Rust's standard path handling for its sensitive components. It would like Rust to support that approach as well.

systemd still wants Rust in its core and says it is open to work with the Rust community to get there.

A wave of rust

systemd is not the only part of the Linux stack making room for Rust.

The memory-safe language entered the Linux kernel with 6.1 as an exploratory effort, with the kernel maintainers declaring late last year that the experiment was over.

Ubuntu has been taking the same route with its core tools since some time now, when sudo-rs and uutils coreutils became the default experiences.

All that strongly suggests that the adoption of Rust will only accelerate as we go into a very busy 2027.



from It's FOSS https://ift.tt/5VEis61
via IFTTT

Rabu, 04 November 2026

Schools That Say No to Google’s AI Will Be Saying Goodbye to its Search Too

google and gemini logos beside a school building

Google is switching off Gemini, Gemini Notebook, Gemini in Classroom, and Search for K-12 student accounts by default. They stay off until a school admin turns them on, and each service can be manually enabled on its own.

But Search is where things get awkward.

As you already know, Google has built AI into it. They say that AI Mode and AI Overviews will guide students through problems step by step in school accounts. That leaves schools that want no AI without Google Search.

Google's VP for Education, Chris Phillips, told The New York Times (paywalled) that students at such a school would be shown a notice from the school saying Search is unavailable.

Some more details

google classroom
Just a placeholder image of Google Classroom.

Starting 2027, schools whose admins turn on AI will get two Search options, one similar to the consumer version with AI overviews and chats available and the other built around Guided Learning, the student version of Gemini.

It would work as a study coach that asks students questions and breaks problems into steps instead of simply handing over answers. Google's post promises schools more details are coming over the next few months before any of this rolls out.

The August changes

Gemini in Classroom opened to students of all ages in August, and the Gemini tab was on by default at schools that already allowed students to use Gemini, while schools that had it off for students kept it off.

Before that, Gemini had only been on for students 18 and older, and admins who wanted it turned off for under-18 users could place them in a separate unit and disable access there.

The change announced now reverses that default.

Two of the largest districts in the U.S. have already acted before this came about, with New York City announcing a ban on student-facing generative AI tools for preschool through eighth grade and Los Angeles Unified blocking them on district-issued devices.

Germans do it differently

A four-year framework contract awarded to IONOS after a recent public procurement process lets schools in Berlin, Hamburg, Hesse, Bremen, North Rhine-Westphalia, and Rhineland-Palatinate opt into a cloud built on Nextcloud's open source software.

No school or school authority is mandated to use it, and the tender specifically asks for software released under an open source license approved by the OSI, citing digital sovereignty and the freedom to change providers.

The distinction that I am trying to make here is that a school in the U.S. that wants Search from Google will have to take its AI along once the changes roll out, whereas the German framework lets schools opt in or stay out.


Suggested Read 📖: Denmark’s Collabora pilot was never the Microsoft replacement it was made out to be.



from It's FOSS https://ift.tt/5BfC4n2
via IFTTT

Local AI Weekly #8: All Your RAM Are Belong to Us

Sam Altman RAM  meme

Sam Altman, the OpenAI CEO, said it's "quite terrible" that so much of the world's limited DRAM sits in personal computers that use it maybe 5% of the time. In his view, it would do more work in a data center.

My take? Idle RAM is the price of owning your computer. Nobody can rent it out from under you, raise its price or switch it off. That ownership is the reason this newsletter exists.

This is a short edition, though. I've been busy lately and couldn't explore much. But there's still enough here to keep your RAM busy for more than 5% of the time. Take that, Altman.

🔍 Discover AI tools

Superwhisper is now on Linux. The popular Mac dictation app now has a native Linux app for system-wide voice input. You can transcribe locally on your own CPU or GPU, or send audio to the cloud for speed. It is NOT open source software.

voiceio is for anyone who'd rather stay open source. This MIT-licensed offline dictation app streams text into whatever app you're using, on both Wayland and X11. It can also fine-tune its model on your own recordings while your computer is idle, and switches to the new model only if it scores better.

OpenKruise Agents is for running AI agent sandboxes on your own Kubernetes cluster. Interesting part is that your API keys never enter the sandbox. An egress proxy pulls them from Kubernetes Secrets and adds them to each request on its way out. It also adds L4/L7 egress rules and an allowlist for MCP tools, so a prompt-injected agent can't wander into your internal network. Overkill for a laptop. Worth a look if you self-host agents for a team.

🐧 AI in the kernel

Linux 7.4-rc1 is out, and its merge window was the biggest on record. 19,671 commits, against 16,267 for 7.3.

Torvalds credits a wave of fixes for bugs that AI found. He'd rather get them during the merge window than as late fixes in the RC stage, and called that a much healthier model. He also admits it may not be all AI. 7.4 is expected to be the next LTS kernel, and a third of the patch is AMD GPU register headers.

👀 Big Tech Watch

Now you know why Altman is eyeing your RAM.

Nikkei Asia reports that off-balance-sheet AI obligations at Alphabet, Microsoft, Amazon, Meta and Oracle grew 90% in six months, to about $3.2 trillion. All those signed data center leases that haven't started yet, plus orders for chips and servers. If they show it in the balance sheet, their shares will tank even further.

Trillions of dollars in data centers need a lot of RAM to fill them. Yours included, apparently (as per Sam Altman).

🗂 AI Jargon: KV cache

When a model generates text, it stores some working data for every token in the conversation so it doesn't have to recalculate it. That store is the KV (key-value) cache.

It grows with context length. That's why a model that runs fine in a short chat can run out of memory once you paste in a long document.

😂 Meme

Plenty more of the memes on our Instagram page.

Sam Altman RAM jokes

What are you exploring on Local AI these days?



from It's FOSS https://ift.tt/yAOWvsg
via IFTTT

Linux 7.4’s Merge Window is Huge, and Linus Says it Might Not Just Be AI

linus torvalds flood of ai comment banner

The story starts when Linus Torvalds opened the Linux 7.4 merge window more than two weeks ago when he was headed out for his annual family vacation, resulting in some temporary disruptions to his work.

He warned that his schedule would be unusual and that merging might need an extra week, but RC1 still arrived according to the usual two-week schedule.

None of that made the window any smaller. By his count, it brought in almost 20,000 commits, where 15,000 or so is the usual number, while the shortlog alone ran to 24,000 lines.

How big is it?

Phoronix went back through every merge window since Linux 3.1 in 2011 and counted the commits in each, with Linux 7.4 coming out on top at 19,671 and Linux 7.3 holding the previous high at 16,267.

That did not come out of nowhere, since every window after 7.0 has topped the one before it, with 7.4 beating 7.3 by more than 3,400 commits.

Development cycles across kernel releases show the same trend, with Linux 7.3 being the first to pass the 20,000 commits mark at 20,260 and the 7.4 window coming quite close to it.

Why?

Linus thinks AI is the reason, pointing to a surge of work set off by AI tools, including bug fixes and a growing number of patches the tools suggested, although those often end up rewritten.

He prefers receiving that work up front in the merge window over dealing with it as late fixes during the release candidate stage. He also hopes that the bigger window means AI fixes are turning into an ordinary part of development.

AI is not his only explanation. He says the expectation that 7.4 will be the next long-term support release may have played a minor role in its size, treating it as a separate driving factor.

What's included?

AMD GPU header files, which hold register definitions, account for about a third of the patch, and Linus tells people to ignore them. Drivers make up about half the patch, or roughly two thirds with the AMD headers counted.

The rest covers core areas such as networking, Rust, BPF, and memory management, along with filesystems, tooling like perf and selftests, and architecture updates.

The source tree grew along with it. By Phoronix's cloc count, the tree stood at 41,605,120 lines ahead of RC1 on November 1, up from 41,019,555 when Linux 7.3 came out.

Actual code accounts for roughly 455,000 lines of that, with the rest being blank lines and comments.


Suggested Read 📖: The latest releases of openEuler come with a heavy dose of AI agents.



from It's FOSS https://ift.tt/AtaqniC
via IFTTT

I Got Tired of Remembering Ports for 30 Docker Apps in My Homelab. Here's How I Fixed it

Tailnet with Tailscale Services

My homelab runs on 5 machines with around 30 Docker containers. For a long time, the way I reached them was a bookmarks folder full of entries like http://100.101.12.4:8096.

Tailscale took care of getting me into my network from anywhere, but once I was in, I was back to remembering which box ran what on which port.

The usual fixes each have a catch. A reverse proxy with your own domain works, but it's one more thing to maintain, and every new app means touching two config files instead of one. Giving each container its own Tailscale sidecar gets you nice names, but it also turns the Machines page into a list of containers, each with its own auth key and state volume.

When Tailscale released Services, it looked like the thing I had actually wanted: names for apps, not for machines. I've been running my containers that way since, and this is a write-up of how it works, how to set it up by hand, and the two things worth knowing before you start.

What a Tailscale Service actually is

A Service is a name plus a virtual IP on your tailnet, for example jellyfin.your-tailnet.ts.net. It doesn't belong to a device. Instead, one or more of your machines advertise it, and Tailscale routes clients to them.

So your Docker host stays a single machine in the tailnet, and the apps on it become Services. Access rules can point at a Service directly, so you can let someone reach Jellyfin without giving them the whole host.

Here is what a request looks like once it's set up:

Request path for a Tailscale Service: a phone on the tailnet opens jellyfin.your-tailnet.ts.net, Tailscale routes it to the Docker host, tailscaled terminates HTTPS and forwards plain HTTP with an X-Forwarded-For header to the Jellyfin container on its Docker network IP

Two details in that picture matter later: tailscaled on the host terminates HTTPS and talks plain HTTP to your app, and it adds an X-Forwarded-For header. To your app, Tailscale behaves like a reverse proxy.

Setting one up by hand

Before automating anything, it's worth doing it manually once for a single container. You need three things.

1. A tagged host. Services are advertised by tagged machines, so give your Docker host a tag:

sudo tailscale up --advertise-tags=tag:server --reset

2. A policy that allows it. In the admin console under Access controls, the tags must exist, and an auto-approver saves you from clicking "Approve" for every Service:

{
  "tagOwners": {
    "tag:server": ["autogroup:admin"],
    "tag:container": ["tag:server"]
  },
  "autoApprovers": {
    "services": {
      "tag:container": ["tag:server"]
    }
  }
}

3. The Service itself. On the Services page of the admin console, choose Advertise, then Define a Service, name it jellyfin and give it the tag:container tag. Then, on the host, point it at the app:

tailscale serve --service=svc:jellyfin --https=443 127.0.0.1:8096

This assumes Jellyfin publishes port 8096 on the host. If HTTPS certificates are enabled for your tailnet (DNS page in the admin console), https://jellyfin.your-tailnet.ts.net now works from every device that runs Tailscale.

For one app, that's fine. For 30, it gets old quickly: one definition in the console and one serve command per app, plus published ports again. And nothing updates by itself when you add, remove or move a container.

Why I ended up scripting it

The part that bothered me most was that the configuration lived in two places. The app was defined in a Compose file, and how to reach it was defined somewhere in the admin console and in tailscaled's state. Remove a container and its Service just stays there.

Traefik solved the same problem for reverse proxies years ago with Docker labels, and I didn't find anything like that for Tailscale Services. So I wrote a small Go program that does it, DockTail. It runs as one container per host, watches Docker events, and turns labels like these into the steps above:

services:
  docktail:
    image: ghcr.io/marvinvr/docktail:latest
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - /var/run/tailscale:/var/run/tailscale
    environment:
      - TAILSCALE_OAUTH_CLIENT_ID=${TAILSCALE_OAUTH_CLIENT_ID}
      - TAILSCALE_OAUTH_CLIENT_SECRET=${TAILSCALE_OAUTH_CLIENT_SECRET}

  jellyfin:
    image: jellyfin/jellyfin:latest
    restart: unless-stopped
    volumes:
      - ./config:/config
      - ./cache:/cache
      - /path/to/media:/media:ro
    labels:
      - "docktail.service.enable=true"
      - "docktail.service.name=jellyfin"
      - "docktail.service.port=8096"
      - "docktail.service.service-port=443"

With an OAuth client (Services: Write, Devices Core: Write, scoped to tag:server) it also creates the Service definition, so the manual console step goes away. Note that Jellyfin has no ports: section: the traffic goes straight to the container's IP on its Docker network.

That's also why this needs something that watches Docker. If you skip published ports and point serve at a container IP by hand, it breaks the next time the container is recreated:

When the Jellyfin container is recreated, its Docker network IP changes from 172.18.0.5 to 172.18.0.9. A serve config written by hand still points at the old address, while a tool that watches Docker events updates it

DockTail is open source (AGPL-3.0). You don't need it to use Services, though, and everything in the next section applies either way.

Two things worth knowing

If a Service doesn't show up, it's almost always the tag. The host needs to be tagged (check tailscale status), and if you haven't set up an auto-approver, the Service waits for you on the Services page.

Apps that check proxy headers may complain at first, because Tailscale adds an X-Forwarded-For header. Home Assistant answers with "400: Bad Request" until you trust the proxy in configuration.yaml:

http:
  use_x_forwarded_for: true
  trusted_proxies:
    - 127.0.0.1

On a Docker bridge network, use the address Home Assistant names in its log instead of 127.0.0.1. Jellyfin has the same setting under Dashboard → Networking → Known proxies, but it works fine without it.

Was it worth switching?

For me, yes. What I like most is that the network config now sits right next to the app in the same Compose file. Adding an app means adding labels, removing it takes its Service with it, and my Machines page shows machines again.

I'd still use a reverse proxy for anything public with my own domain. Tailscale Funnel is great for a webhook or sharing one thing with a friend, but it's not a replacement for that. And if you run a handful of containers that rarely change, the manual tailscale serve route above is perfectly fine and needs no extra tool.

If you want to try the label approach, the code and docs (including complete guides for Jellyfin and Home Assistant) are at github.com/marvinvr/docktail and docktail.org/docs.

And if you've solved this differently, I'd honestly like to hear how. Leave a comment, I'll read all of them.

✍️
The article is contributed by Marvin von Rappard, creator of open source project Docktail.


from It's FOSS https://ift.tt/LHuyR46
via IFTTT